在 HarmonyOS 应用开发中,JSON 数据处理是网络请求、本地数据持久化以及跨模块通信的核心基础。由于 HarmonyOS 的本地存储(如 Preferences、PersistentStorage)以及网络传输通常只支持基础数据类型(如 string),开发者必须熟练掌握对象与 JSON 字符串之间的转换。
以下梳理 JSON 序列化与反序列化实战:
一、 基础序列化与反序列化
对于简单的对象或数组,可以直接使用内置的JSON对象进行转换。
- 序列化(对象转字符串):使用
JSON.stringify()。 - 反序列化(字符串转对象):使用
JSON.parse()。
实战示例(网络请求场景):
在发送 POST 请求时,需要将请求体序列化为 JSON 字符串;在接收响应时,需要将返回的 JSON 字符串解析为具体的接口类型。
import { http } from '@kit.NetworkKit'; interface User { id: number; name: string; } async function fetchUser(userId: number): Promise<User> { let httpRequest = http.createHttpRequest(); try { // 1. 序列化:将对象转为 JSON 字符串作为请求体 let requestBody = JSON.stringify({ userId: userId }); let result = await httpRequest.request('https://api.example.com/user', { method: http.RequestMethod.POST, header: { 'Content-Type': 'application/json' }, extraData: requestBody }); // 2. 反序列化:将服务端返回的 JSON 字符串解析为 User 对象 if (result.responseCode === 200) { let user = JSON.parse(result.result as string) as User; return user; } } catch (error) { console.error('请求失败:', error); throw error; } finally { httpRequest.destroy(); } }二、 高级序列化:指定部分属性
在实际业务中,有时出于安全或性能考虑,不希望将对象的所有属性都暴露或存储。JSON.stringify()的第二个参数replacer支持传入一个字符串数组,用于指定需要序列化的属性。
实战示例:
interface Person { name: string; age: number; city: string; } let obj: Person = { name: 'John', age: 30, city: 'ChongQing' }; // 仅序列化 name 属性 let jsonStr = JSON.stringify(obj, ['name']); console.info(jsonStr); // 输出: {"name":"John"}三、 本地持久化存储中的 JSON 处理
HarmonyOS 提供的轻量级键值对存储Preferences仅支持string、number、boolean等基础类型。存储复杂对象(如列表、自定义类)时,必须进行序列化。
最佳实践:封装数据访问层(Repo)
为了避免在业务逻辑中频繁处理字符串转换,建议将序列化逻辑封装在数据访问层中,确保上层业务始终操作的是强类型对象:
export class TodayRepo { // 读取并反序列化 async get(dateKey: string): Promise<DailyEntry | null> { const raw = await this.prefs.getString(toKey(dateKey)); if (!raw) return null; try { const parsed = JSON.parse(raw) as DailyEntry; return isValidEntry(parsed) ? parsed : null; // 增加合法性校验 } catch (_err) { return null; } } // 序列化并写入 async set(entry: DailyEntry): Promise<void> { await this.prefs.setString(toKey(entry.date), JSON.stringify(entry)); } }四、 核心避坑指南:带方法的对象数组
这是开发中最容易引发崩溃的场景。JSON.stringify()和JSON.parse()只能处理纯数据,无法保留对象的原型链(即类中的方法)。如果直接将带有方法的对象数组存入PersistentStorage,读取后调用方法会导致应用崩溃。
解决方案:
- 无方法的纯数据类:直接
JSON.parse()解析即可。 - 带方法的类:反序列化后,必须通过
map遍历数据,调用类的构造函数重新实例化对象,以恢复原型链。
实战示例:
class Student { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } // 带有方法的类 selfIntroduction(): string { return `I am ${this.name}, ${this.age} years old.`; } } // 存储时:转为字符串 let jsonStr = JSON.stringify([new Student('Tom', 16)]); // 读取时:必须重新构造对象数组 let dataArr = JSON.parse(jsonStr); let studentArr = dataArr.map((item: Student) => new Student(item.name, item.age)); // 此时才能安全调用方法 studentArr[0].selfIntroduction();五、 特殊数据结构处理:HashMap
JSON.stringify()默认不支持直接对HashMap进行操作。如果需要将HashMap序列化(例如传递给 Native 侧或进行网络传输),需要先将其转换为普通的Record对象:
function map2rec(map: HashMap<string, ESObject>): Record<string, ESObject> { let rec: Record<string, ESObject> = {}; map.forEach((value: ESObject, key: string) => { if (value instanceof HashMap) { rec[key] = map2rec(value); // 递归处理嵌套的 HashMap } else { rec[key] = value; } }); return rec; } let myRec = map2rec(hashMap); let str = JSON.stringify(myRec); // 正常序列化在掌握了基础的 JSON 序列化、反序列化以及对象方法恢复等实战技巧后,针对 HarmonyOS 的企业级开发,我们还需要应对状态管理持久化、跨设备流转、第三方工具库集成以及底层字节流转换等更高级的数据处理场景。以下从四个维度进行深度扩展:
一、 状态管理持久化:V1 与 V2 的 JSON 处理差异
在 HarmonyOS 中,将复杂对象存入AppStorage或PersistentStorage时,底层都会进行 JSON 序列化。不同版本的状态管理在底层机制上存在显著差异:
- V1 时代的 PersistentStorage:它仅支持基础类型。存储对象或数组时,开发者必须手动调用
JSON.stringify()转为字符串,读取时再用JSON.parse()还原。若对象包含方法,还需通过map遍历重新调用构造函数(如前文所述)。 - V2 时代的 PersistenceV2:引入了“劫持-代理”双阶段机制。开发者只需使用
@ObservedV2和@Trace装饰器,框架会在编译期注入拦截器。当数据发生改变时,PersistenceV2会在后台自动进行脏值检查、JSON 序列化,并通过异步队列合并 I/O 操作写入磁盘。- 避坑指南:
PersistenceV2对数据类型要求极其严苛,仅支持基础类型及其构成的Array或Object。如果试图将PixelMap(图片对象)或复杂的闭包函数塞入,运行时会直接报错。
- 避坑指南:
二、 跨设备流转:Base64 字节流与 JSON 的无缝转换
在 HarmonyOS NEXT 的 Share Kit(碰一碰分享)或跨设备流转(onContinue)场景中,复杂的 JSON 数据通常不能直接以明文传输,而是需要转换为高抗干扰的字节流:
- 发送端(序列化与编码):首先将内存中的结构化 JSON 对象序列化为字符串,然后利用
util.TextEncoder将其转化为Uint8Array字节流,最后进行 Base64 编码,拼装在自定义的外链(如aeroplan://import_plan)中。 - 接收端(拦截与反序列化):当 B 设备通过 Scheme 拦截到外链后,提取 Base64 字符串,解码还原为 JSON 字符串,再解析为具体的业务对象,最终写入本地关系型数据库(RDB)。
这种设计将跨应用的数据流转操作大幅压缩,消除了繁琐的手动复制粘贴体验。
三、 第三方工具库集成:harmony-utils
除了系统自带的JSON对象,在实际工程中,推荐使用社区成熟的工具库(如@pura/harmony-utils)来简化复杂的 JSON 操作。该库提供了丰富的 API:
- 类型安全转换:通过
JSONUtil.jsonToBean(jsonStr, User)可以直接将 JSON 字符串安全地转换为指定的 Class 实例,避免了手动as断言的繁琐。 - 复杂结构解析:支持
jsonToArray(转数组)、jsonToMap(转 Map)以及mapToJsonStr(Map 转字符串)。 - 格式校验:提供
isJSONStr()方法,在处理外部传入的不可靠数据时,可先进行合法性校验,防止JSON.parse()抛出异常导致应用崩溃。
四、 底层数据处理:超长字符串与二进制流
在某些极客场景下(如大文件传输、长文本加密),普通的 JSON 字符串可能会遇到 URL 长度限制或内存瓶颈。此时需要结合底层 API 进行处理:
- TextEncoder / TextDecoder:当 JSON 字符串过长时,可以利用鸿蒙的
util.TextEncoder将字符串直接映射为Uint8Array。这种直接内存映射操作比普通的循环转换性能高出 60% 以上,非常适合在网络请求的extraData中传输二进制格式的 JSON 数据。 - 流式处理:对于超大 JSON 文件,避免一次性
JSON.parse()导致主线程 OOM(内存溢出),应考虑结合文件流(fs)进行分块读取,或采用 SAX 模式的流式解析器,边读取边处理节点数据。