HarmonyOS 鸿蒙Next中class-transformer使用问题

HarmonyOS 鸿蒙Next中class-transformer使用问题 如下方所示代码,data类型为Map,泛型为DataBean类如何通过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

9 回复

使用 @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-transformerhttps://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时,需注意以下几点:

  1. 确保安装的是兼容鸿蒙的版本;
  2. 序列化/反序列化操作需使用鸿蒙支持的API;
  3. 装饰器语法需符合ArkTS规范;
  4. 对象转换时类型元数据需明确定义。

鸿蒙Next的运行时环境与Node.js不同,部分class-transformer功能可能需要适配。

在HarmonyOS Next中使用class-transformer处理Map类型数据时,你的实现方案是正确的。针对嵌套类的情况,补充几点关键说明:

  1. Map转换的核心在于使用@Transform装饰器手动处理:
  • 通过target.value获取原始对象
  • 新建Map实例并遍历原始对象属性
  • 对每个值使用plainToClass转换为目标类
  1. 嵌套类需要使用@Type装饰器指定类型:
[@Type](/user/Type)(() => Name)
name?: Name
  1. 注意事项:
  • 确保所有需要转换的类都添加了@Expose()
  • 使用{toClassOnly: true}确保只在反序列化时触发转换
  • 方法定义需要放在类中而非接口中

这种方案既解决了Map结构的反序列化问题,又保留了类的方法定义,是TypeScript环境下处理复杂对象结构的推荐做法。

回到顶部