You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 00:45:53