HarmonyOS 鸿蒙Next中class-transformer使用问题
HarmonyOS 鸿蒙Next中class-transformer使用问题
如下方所示代码,data
类型为Map
,泛型为Data
。Bean
类如何通过class-transformer
库,进行json字符串反序列化。
需求背景是Bean.data
中字段名不固定,需要通过字段名遍历查找对应数据。同时Data
还要保留成员方法。
使用JSON.parse
只会将对象转换为object
,无法遍历字段名、无法调用成员方法。
三方库依赖版本:
'reflect-metadata': '^0.1.13',
'class-transformer': '^0.5.1'
经过测试,我的需求场景更复杂一些。在Map.value
类中有嵌套的子类,类型如下:
export class Bean {
data?: Map<string, Data>
}
export class Data {
name?: Name
age?: number
getAge(): string | undefined {
return this.age
}
}
export class Name {
name?: string
getName(): string | undefined {
return this.name
}
}
最终结合了@哈莫尼OS 的方案,我将实现修改成:
export class Bean {
@Expose()
@Transform((target) => {
const value: object = target['value']
const map = new Map<string, Data>()
Object.keys(value).forEach(key => {
map.set(key, plainToClass(Data, value[key]))
})
return map
}, { toClassOnly: true })
data?: Map<string, Data>
}
export class Data {
@Type(() => Name)
name?: Name
age?: number
getAge(): string | undefined {
return this.age
}
}
export class Name {
name?: string
getName(): string | undefined {
return this.name
}
}
更多关于HarmonyOS 鸿蒙Next中class-transformer使用问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html
使用 @Expose 装饰器标注需要序列化的属性,并为 Map 类型字段添加自定义转换逻辑:
import { Expose, Transform, Type } from 'class-transformer';
export class Bean {
[@Expose](/user/Expose)()
@Transform(({ value }) => {
if (value instanceof Map) return value;
// 自定义反序列化逻辑:将普通对象转Map
const map = new Map<string, Data>();
Object.keys(value).forEach(key => {
map.set(key, plainToClass(Data, value[key]));
});
return map;
}, { toClassOnly: true })
data?: Map<string, Data>;
}
export class Data {
[@Expose](/user/Expose)()
name?: string;
[@Expose](/user/Expose)()
age?: number;
getName(): string | undefined {
return this.name;
}
}
使用 plainToClass 方法将 JSON 转换为类实例,启用 exposeDefaultValues 选项:
import { plainToClass } from 'class-transformer';
import "reflect-metadata";
const jsonStr = `{
"data": {
"key1": { "name": "John", "age": 30 },
"key2": { "name": "Alice", "age": 25 }
}
}`;
const beanInstance = plainToClass(Bean, JSON.parse(jsonStr), {
exposeDefaultValues: true,
enableCircularCheck: true
});
更多关于HarmonyOS 鸿蒙Next中class-transformer使用问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
不需要三方库,你可以这么取值:
假如json字符串如下:
const json = '{"a": {"name": "张三","age":23}, "b": {"name": "李四","age":10}}';
实体类定义:
export class Data {
name?: string
age?: number
getName(): string | undefined {
return this.name
}
}
const obj = JSON.parse(json) as object;
const data = obj["a"] as Data
console.log(data.name)
输出:张三
【背景知识】
JSON.parse 将字符串转成对象,对象类型为 Object,只能调用 Object 类型的方法。
如声明一个 Bean 类,类中自定义成员方法,通过 JSON.parse 将字符串转成 Bean 对象后,无法调用 Bean 类中成员方法,如果想调用 Bean 类中自定义的方法,可以通过引入 class-transformer 库,通过 plainToClass 转换 Object 转为类实例。
【分析结论】
Map 在 ArkTS 中,为独立的数据结构,具有独立的原型链和方法(如 set/get),并非类实例,plainToClass 作用是将普通的 Object 对象转成类实例,所以无法通过 plainToClass 将字符串转成 Map。
【修改建议】
使用 Record 替换 Map,Map 是标准的键值对集合,Record 是对象类型,本质是普通对象的扩展,替换后代码如下:
import { plainToClass, Type } from 'class-transformer';
import "reflect-metadata";
import { JSON } from '@kit.ArkTS';
export class Bean {
@Type(() => Data)
// 使用 Record 替换 Map
data?: Record<string, Data>
}
export class Data {
name?: string
age?: number
getName(): string | undefined {
return this.name
}
}
export function test() {
const jsonStr = '{"data":{"a":{"name":"张三","age":1},"b":{"name":"李四","age":2}}}'
let bean = plainToClass(Bean, JSON.parse(jsonStr) as Bean)
if (bean && bean.data) {
const record = bean.data;
const keys = Object.keys(record);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
// 对于 Record 中的对 data,想要调用 data 成员方法,需要通过 plainToClass 再转一次
let data = plainToClass(Data, record[key] as Data)
console.info(`调用 data 方法: ${data.getName()}`);
}
}
}
【总结】
- Map 为键值对集合,并非对象实例,不能通过 plainToClass 直接转换。
【常见 FAQ】 Q:通过 JSON.stringify(Map) 为什么转换后的字符串为 ‘{}’,没有数据? A:由于 JSON.stringify 会调用 Map 对象的 toJSON() 方法,默认 Map 没有实现这个方法,所以打印结果为空对象 ‘{}’.
感谢回复,我的需求场景比示例更复杂一些,最终结合了@哈莫尼OS完成了实现。
看了好几遍,没看懂描述的意思,Map<Data>,Data类型不是固定了吗?为啥说不固定?是key不固定,value固定?
Map这里我写漏了key,类型应该如下:
export class Bean {
data?: Map<string, Data>;
func() {
}
}
期望解析的数据:'{"a": {"data": 1}, "b": {"data": 1}}'
。
直接用JSON.parse
的话会丢失Bean/Data
这些类型,也就无法调用其中的方法。
参考官方文档,我使用了class-transformer
:
https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-arkts-75
用class-transformer
的话,注解加上后如下所示。但是Data
是泛型,用class-transformer
的话无法序列化为Map<string, Data>
export class Bean {
@Type(() => Data)
data?: Map<string, Data>;
func() {
}
}
在HarmonyOS Next中使用class-transformer时,需注意以下几点:
- 确保安装的是兼容鸿蒙的版本;
- 序列化/反序列化操作需使用鸿蒙支持的API;
- 装饰器语法需符合ArkTS规范;
- 对象转换时类型元数据需明确定义。
鸿蒙Next的运行时环境与Node.js不同,部分class-transformer功能可能需要适配。
在HarmonyOS Next中使用class-transformer处理Map类型数据时,你的实现方案是正确的。针对嵌套类的情况,补充几点关键说明:
- Map转换的核心在于使用@Transform装饰器手动处理:
- 通过
target.value
获取原始对象 - 新建Map实例并遍历原始对象属性
- 对每个值使用
plainToClass
转换为目标类
- 嵌套类需要使用@Type装饰器指定类型:
[@Type](/user/Type)(() => Name)
name?: Name
- 注意事项:
- 确保所有需要转换的类都添加了@Expose()
- 使用
{toClassOnly: true}
确保只在反序列化时触发转换 - 方法定义需要放在类中而非接口中
这种方案既解决了Map结构的反序列化问题,又保留了类的方法定义,是TypeScript环境下处理复杂对象结构的推荐做法。