JSON 解析与 ArkTS 序列化

一、引言

JSON 是应用数据流转的事实标准:服务端响应、本地缓存、组件 key 生成、调试日志输出都离不开它。在多设备短视频项目中,JSON.stringify 被大量用于列表 key 生成与日志输出,而 JSON.parse 则是网络数据(第 38 篇)进入模型层(第 36 篇)的必经之路。

但 JSON 与 ArkTS 的"对象"并不等价:JSON 只有对象、数组、字符串、数字、布尔与 null 六种类型,而 ArkTS 的 class 可以携带方法、联合类型字段与运行时资源引用。从 JSON 到类型安全的模型实例,需要一套明确的转换规范。本文结合项目真实用法,讲解 JSON.parse/stringify 基础、class 类型安全解析、复杂嵌套结构、ArkTS 序列化限制以及 mock/网络数据的转换路径。
breakpoint-system

二、JSON.parse 与 JSON.stringify 基础

两个 API 互为逆操作:stringify 把对象序列化为字符串,parse 把字符串还原为对象。项目中最常见的用法是作为列表 key 与日志输出:

// features/multishortvideoindividual/src/main/ets/view/Works.ets(节选)
Repeat<WorksDataModel>(this.worksData)
  .each((item: RepeatItem<WorksDataModel>) => {
    // ...GridItem 渲染
  })
  .key((item: WorksDataModel) => JSON.stringify(item))   // 用序列化结果做唯一 key
// features/multishortvideoadaptivevideo/src/main/ets/view/AdaptiveAVPlayer.ets(节选)
Logger.info(TAG, `changePortraitVideo immersionInfo = ${JSON.stringify(immersionInfo)}`);

stringify 的序列化规则值得牢记:默认仅序列化可枚举的自身属性;undefined、函数、Symbol 会被静默省略;NaNInfinity 转为 null;数组中的 null/undefined 保留为 null。这些规则决定了"模型类只有数据字段"(第 36 篇规范)是对象可稳定序列化的前提——一旦模型混入方法或资源对象,序列化结果将不再可靠。

三、class 与接口的类型安全解析

JSON.parse 返回类型是 object | null(实际语义接近 any),直接访问字段没有任何编译期保护。将 JSON 还原为 class 实例必须显式转换:

// 安全的反序列化:JSON -> AvDataSourceModel
function fromJson(json: string): AvDataSourceModel[] {
  const raw = JSON.parse(json) as Array<{ currentSource?: string }>;
  return raw.map((item) => new AvDataSourceModel(item.currentSource ?? ''));
}

这里的关键不是"强转",而是逐字段校验as 只欺骗编译器,字段缺失时运行结果仍是 undefined。对于服务端可能缺字段的数据,应在构造函数层面兜底——项目模型构造函数中的 ?? 默认值(如 MSVDataModeliconOnly ?? false)正是为此设计。反例是直接 JSON.parse(body) as CommentDataModel[] 后取 item.pic,一旦服务端未返回该字段,undefined 就会泄漏到 UI 层引发渲染异常。规范总结为:接口定义类型形状,class 提供构造兜底,两层配合实现类型安全

四、复杂嵌套结构的解析

评论接口的响应是典型的嵌套结构:外层是评论数组,每条评论又含回复数组。解析时采用"逐层映射"策略:

// 嵌套 JSON -> CommentDataModel[](与 CommentViewModel 数据结构对应)
function parseComments(body: string): CommentDataModel[] {
  const raw = JSON.parse(body) as Array<{
    id?: number; name?: string; content?: string; likes?: string;
    date?: string; location?: string;
    replys?: Array<{ id?: number; name?: string; content?: string }>;
  }>;
  return raw.map((item) => new CommentDataModel(
    item.id, undefined, item.name, item.likes, item.content, item.date, item.location,
    item.replys?.map((r) => new ReplyDataModel(r.id, r.name, r.content))
  ));
}

注意 pic 字段(PixelMap | ResourceStr | DrawableDescriptor)在此处传 undefined——位图与资源引用属于运行时对象,无法从 JSON 还原。正确的做法是传输层只传资源标识(如 ic_user01),解析层(viewModel)再映射为 $r('app.media.ic_user01')。这正是"传输数据"与"展示数据"的分层:JSON 承载可序列化的基础类型,资源映射收敛在模型转换的边界上。

五、ArkTS 序列化限制与 @ohos.util 辅助

ArkTS 对运行时对象能力有严格约束,序列化相关限制尤其值得注意:

限制说明规避方案
禁止 eval/new Function无法动态执行代码静态 map 转换
对象字面量受限Record 任意键操作用 class 承载结构
函数不可序列化stringify 静默丢弃方法模型类只存数据
联合类型字段pic 等复杂类型无法直传传输层用字符串标识
循环引用stringify 直接抛异常模型禁止互相回指

@kit.ArkTS@ohos.util 提供 JSON 之外的辅助工具,例如用 util.generateRandomUUID() 生成稳定的列表 key,替代字符串化大对象:

import { util } from '@kit.ArkTS';

const uuid = util.generateRandomUUID();   // 生成全局唯一 ID
// 可用作评论临时 id:new CommentDataModel(uuid, ...)

对于大对象序列化,项目还常借助 JSON.parse(JSON.stringify(obj)) 做深拷贝。要注意其性能开销随对象体积线性增长,且会丢失 undefined 与函数——频繁或大规模场景应改用显式字段拷贝。

六、本地 mock 与真实网络数据的转换

项目的 viewModel(如 CommentViewModelWorksViewModel)目前以 $r() 资源直接构造模型。演进为网络数据源后,需在 viewModel 内完成"JSON ↔ 模型 ↔ 资源"三层转换,且保持对外接口(getCommentList() 等)不变:

// 统一入口:本地 mock 与网络数据都转换为 CommentDataModel[],UI 无感知
export class CommentViewModel {
  private commentList: CommentDataModel[] = [];

  async load(): Promise<void> {
    // 1. 本地 mock(现有逻辑,作为兜底)
    // 2. 网络 JSON(第 38 篇):parseComments(body) 得到模型
    // 3. 资源映射:name/likes 由 JSON 字符串转为 $r() 或原样字符串
  }

  getCommentList(): CommentDataModel[] {
    return this.commentList;
  }
}

转换规范遵循三原则:传输层只用基础类型(string/number/boolean/数组),保证 JSON 可序列化;展示资源在 viewModel 层映射,模型字段与资源 API 解耦;解析失败降级到 mock 数据,任何 JSON 异常都不让 UI 空白。这样 JSON 解析逻辑与业务 UI 完全解耦,本地开发与线上环境切换只改 load() 内部实现。

七、总结与最佳实践

  • stringify 的 key 用法适合小对象;大数据集改用 idutil.generateRandomUUID() 提升性能;
  • JSON.parse 返回值的 as 强转不提供运行时保护,必须逐字段校验并在构造函数兜底;
  • 嵌套结构采用"逐层 map 到模型类"的解析策略,与第 36 篇的嵌套模型一一对应;
  • 位图、资源、函数等运行时对象不可 JSON 化,传输层用标识字符串,展示层再映射;
  • ArkTS 禁止 eval/new Function,序列化只适用于纯数据 class,模型禁止循环引用;
  • 深拷贝用 JSON.parse(JSON.stringify(obj)) 有性能与语义损耗,大数据量改显式拷贝;
  • 转换逻辑统一收敛在 viewModel,mock 与网络数据通过同一接口对外,保证 UI 无感切换;
  • 解析失败一律降级到 mock 或空列表,任何 JSON 异常不得抛到 UI 层。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐