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

NestJS中class-transformer多子类型序列化异常问题

NestJS class-transformer鉴别器序列化异常:所有枚举类型被转为第一个子类型

问题场景

我用NestJS配合class-validator/transformer开发,有一个包含两种类型的数组字段。原本通过@Type装饰器加鉴别器定义数组项类型,同时用@Transform避免序列化出空对象,但现在遇到了问题:

鉴别器是个有7个值的枚举,其中6个对应ClassONE,1个对应ClassTWO,但序列化时不管原枚举类型是什么,都会被转换成配置里第一个子类型的枚举值。

代码示例

enum DiscrType {
  A = "a",
  B = "b",
  C = "c"
}

class Discriminator {
  @IsEnum(DiscrType)
  type: DiscrType;
}

class ClassONE extends Discriminator {
  // 自定义属性
}

class ClassTWO extends Discriminator {
  // 自定义属性
}

class Stuff {
  @Type(() => Discriminator, {
    discriminator: {
      property: 'type',
      subTypes: [
        { value: ClassTWO, name: DiscrType.C},
        { value: ClassONE, name: DiscrType.A}, // 原类型是B也会被转为A
        { value: ClassONE, name: DiscrType.B},
      ],
    },
    keepDiscriminatorProperty: true,
  })
  @Transform(
    ({ value }) =>
      value?.map((object: ClassONE | ClassTWO ) =>
        object.type === DiscrType.C
          ? plainToClass(ClassTWO, object)
          : plainToClass(ClassONE, object),
      ),
  )
  @ValidateNested({each: true})
  property: (ClassONE | ClassTWO )[];
}

表现

创建实例new Stuff({type: DiscrType.B, ...})后用Class Serializer序列化:

  • 预期结果:{type: "b", ...}
  • 实际结果:{type: "a", ...}

问题原因

class-transformer的鉴别器逻辑中,当多个子类型配置对应同一个类(比如DiscrType.A和DiscrType.B都指向ClassONE),序列化时会自动匹配第一个符合类的子类型配置,把所有ClassONE实例的type字段替换成第一个配置的DiscrType.A,覆盖了原有的枚举值。

解决方案

方案一:保留鉴别器,修复字段覆盖问题

移除重复的ClassONE子类型配置,同时在@Transform中强制保留原type值:

class Stuff {
  @Transform(({ value }) => {
    if (!value) return [];
    return value.map((object) => {
      const targetClass = object.type === DiscrType.C ? ClassTWO : ClassONE;
      const instance = plainToClass(targetClass, object);
      // 强制保留原始type值,避免被鉴别器覆盖
      instance.type = object.type;
      return instance;
    });
  })
  @ValidateNested({ each: true })
  @Type(() => Discriminator, {
    discriminator: {
      property: 'type',
      subTypes: [
        { value: ClassTWO, name: DiscrType.C },
        { value: ClassONE, name: DiscrType.A }, // 只保留一个ClassONE映射
      ],
    },
    keepDiscriminatorProperty: true,
  })
  property: (ClassONE | ClassTWO)[];
}

方案二:简化配置,用动态类型处理

直接去掉鉴别器的多子类型配置,通过@Type的动态函数处理反序列化类型,@Transform处理序列化逻辑:

class Stuff {
  @Transform(({ value }) => {
    if (!value) return [];
    return value.map((object) => {
      const targetClass = object.type === DiscrType.C ? ClassTWO : ClassONE;
      return plainToClass(targetClass, object);
    });
  })
  @ValidateNested({ each: true })
  @Type(({ object }) => {
    // 反序列化时根据当前对象的type动态返回对应类
    return object.type === DiscrType.C ? ClassTWO : ClassONE;
  })
  property: (ClassONE | ClassTWO)[];
}

这个方案更简洁,完全避开了鉴别器的默认行为冲突,同时保证反序列化和序列化都能正确处理类型和字段。

内容的提问来源于stack exchange,提问作者Branchverse

相关产品推荐
方舟 Agent Plan

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

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