TypeScript含嵌套Map类的序列化与反序列化问题(class-transformer)
问题
我用TypeScript写了几个带嵌套Map的类,用class-transformer做JSON序列化和反序列化时遇到两个问题:
- 父类
TimeStampedObject里的created字段反序列化后没转成Date类型,还是字符串 Group类的profiles字段是Map<string, Profile>,反序列化后变成Map<string, any>,里面的Profile实例没被正确还原
类代码如下:
// 唯一标识符类型 export type Uid = string; export class UidObject { // 对象唯一ID readonly uid: Uid; constructor(uid: Uid) { this.uid = uid; } } export class TimeStampedObject extends UidObject { @Type(() => Date) readonly created: Date; constructor(uid: Uid, created: Date) { super(uid); this.created = created; } } export class Profile extends TimeStampedObject { // 用户显示别名 readonly alias: string; constructor( uid: Uid, created: Date, alias: string, ) { super(uid, created); this.alias = alias; } } export class Group extends UidObject { readonly name: string; @Type(() => Map) // 这里不知道该怎么配置 readonly profiles: Map<string, Profile>; constructor( uid: Uid, name: string, profiles: Map<string, Profile>, ) { super(uid); this.name = name; this.profiles = profiles; } }
测试代码:
let profiles = new Map(); profiles.set("uid-prof-1", new Profile("uid-prof-1", new Date(), "alias-prof-1")); profiles.set("uid-prof-2", new Profile("uid-prof-2", new Date(), "alias-prof-2")); let group = new Group("uid-coco", "Coco", profiles); let json = instanceToPlain<Group>(group); let instance = plainToInstance<Group, any>(Group, json); console.log(group); console.log("------ After =>"); console.log(instance); // 输出的实例格式不对
解决方法
1. 修复父类Date字段的反序列化
class-transformer的@Type(() => Date)在处理继承字段时,需要配合手动转换确保字符串转成Date。修改TimeStampedObject的created字段:
export class TimeStampedObject extends UidObject { @Type(() => Date) // 只在反序列化到类实例时执行转换 @Transform(({ value }) => new Date(value), { toClassOnly: true }) readonly created: Date; constructor(uid: Uid, created: Date) { super(uid); this.created = created; } }
2. 修复Map< string, Profile >的类型转换
class-transformer默认不支持Map的嵌套类型解析,需要自定义转换逻辑,把JSON对象转回Map的同时,把每个值转成Profile实例。修改Group的profiles字段装饰器:
export class Group extends UidObject { readonly name: string; @Transform(({ value }) => { // 序列化时:把Map转成普通对象,方便JSON处理 if (value instanceof Map) { return Object.fromEntries(value); } // 反序列化时:把对象转回Map,并逐个转换值为Profile实例 const map = new Map<string, Profile>(); Object.entries(value).forEach(([key, val]) => { map.set(key, plainToInstance(Profile, val, { excludeExtraneousValues: true, enableImplicitConversion: true })); }); return map; }) readonly profiles: Map<string, Profile>; constructor( uid: Uid, name: string, profiles: Map<string, Profile>, ) { super(uid); this.name = name; this.profiles = profiles; } }
3. 调整反序列化参数
调用plainToInstance时加上配置,确保继承字段被处理,且只保留类中定义的字段:
let instance = plainToInstance<Group, any>(Group, json, { excludeExtraneousValues: true, // 过滤JSON中类没有的字段 enableImplicitConversion: true, // 启用基础类型的隐式转换 exposeUnsetFields: false, // 不暴露未初始化的字段 });
修改后完整代码
import { Type, Transform, instanceToPlain, plainToInstance } from "class-transformer"; // 唯一标识符类型 export type Uid = string; export class UidObject { // 对象唯一ID readonly uid: Uid; constructor(uid: Uid) { this.uid = uid; } } export class TimeStampedObject extends UidObject { @Type(() => Date) @Transform(({ value }) => new Date(value), { toClassOnly: true }) readonly created: Date; constructor(uid: Uid, created: Date) { super(uid); this.created = created; } } export class Profile extends TimeStampedObject { // 用户显示别名 readonly alias: string; constructor( uid: Uid, created: Date, alias: string, ) { super(uid, created); this.alias = alias; } } export class Group extends UidObject { readonly name: string; @Transform(({ value }) => { if (value instanceof Map) { return Object.fromEntries(value); } const map = new Map<string, Profile>(); Object.entries(value).forEach(([key, val]) => { map.set(key, plainToInstance(Profile, val, { excludeExtraneousValues: true, enableImplicitConversion: true })); }); return map; }) readonly profiles: Map<string, Profile>; constructor( uid: Uid, name: string, profiles: Map<string, Profile>, ) { super(uid); this.name = name; this.profiles = profiles; } }
运行测试代码后,instance的结构会和原始group完全一致:profiles是Map<string, Profile>类型,每个Profile的created字段都是Date实例。
内容的提问来源于stack exchange,提问作者pcba-dev
相关产品推荐
相关产品推荐

