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

如何在Nest.js中按条件验证同名字段的不同DTO类型

解决Nest.js中条件DTO字段的验证问题

在TypeScript中,同一类无法重复定义同名属性,所以你不能为postData写两个声明。下面是两种可行的实现方案,结合class-validator和class-transformer来实现根据type动态匹配postData的结构验证:

方案一:联合类型+动态类型转换+自定义验证

这种方案先通过动态类型转换将postData转为对应DTO实例,再验证其结构是否符合要求。

import { IsEnum, ValidateNested, ValidateIf, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments, registerDecorator, ValidationOptions, validate } from 'class-validator';
import { Type } from 'class-transformer';
import { EmailType } from './your-email-type.enum';
import { AccountSuccessDto } from './account-success.dto';
import { MainInquiryDto } from './main-inquiry.dto';

// 自定义验证器:确保postData与当前type匹配
@ValidatorConstraint({ name: 'postDataMatchesType', async: true })
export class PostDataMatchesTypeValidator implements ValidatorConstraintInterface {
  async validate(postData: unknown, args: ValidationArguments) {
    const { type } = args.object as EmailTypeDto;
    let targetDto: any;

    switch (type) {
      case EmailType.ACCOUNT_SUCCESS:
        targetDto = Object.assign(new AccountSuccessDto(), postData);
        break;
      case EmailType.MAIN_INQUIRY:
        targetDto = Object.assign(new MainInquiryDto(), postData);
        break;
      default:
        return false;
    }

    // 验证目标DTO实例
    const errors = await validate(targetDto);
    return errors.length === 0;
  }

  defaultMessage(args: ValidationArguments) {
    const { type } = args.object as EmailTypeDto;
    return `postData must match the structure required for email type: ${type}`;
  }
}

// 注册自定义装饰器
export function PostDataMatchesType(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      constraints: [],
      validator: PostDataMatchesTypeValidator,
    });
  };
}

export class EmailTypeDto {
  @IsEnum(EmailType)
  public type: EmailType;

  @ValidateIf((o) => [EmailType.ACCOUNT_SUCCESS, EmailType.MAIN_INQUIRY].includes(o.type))
  @Type((opts) => {
    // 根据当前对象的type动态返回DTO类
    const { type } = opts.object as EmailTypeDto;
    switch (type) {
      case EmailType.ACCOUNT_SUCCESS:
        return AccountSuccessDto;
      case EmailType.MAIN_INQUIRY:
        return MainInquiryDto;
      default:
        return null;
    }
  })
  @ValidateNested()
  @PostDataMatchesType()
  postData: AccountSuccessDto | MainInquiryDto;
}

方案二:纯自定义验证器(无需类型转换)

如果不需要将postData转为具体DTO实例,仅需验证结构,可直接在自定义验证器中完成逻辑:

import { IsEnum, ValidateIf, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments, registerDecorator, ValidationOptions, validate } from 'class-validator';
import { EmailType } from './your-email-type.enum';
import { AccountSuccessDto } from './account-success.dto';
import { MainInquiryDto } from './main-inquiry.dto';

@ValidatorConstraint({ name: 'postDataMatchesType', async: true })
export class PostDataMatchesTypeValidator implements ValidatorConstraintInterface {
  async validate(postData: unknown, args: ValidationArguments) {
    const { type } = args.object as EmailTypeDto;
    let validationResult = false;

    switch (type) {
      case EmailType.ACCOUNT_SUCCESS:
        validationResult = (await validate(Object.assign(new AccountSuccessDto(), postData))).length === 0;
        break;
      case EmailType.MAIN_INQUIRY:
        validationResult = (await validate(Object.assign(new MainInquiryDto(), postData))).length === 0;
        break;
      default:
        validationResult = false;
    }

    return validationResult;
  }

  defaultMessage(args: ValidationArguments) {
    const { type } = args.object as EmailTypeDto;
    return `postData structure does not match required format for type ${type}`;
  }
}

export function PostDataMatchesType(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      constraints: [],
      validator: PostDataMatchesTypeValidator,
    });
  };
}

export class EmailTypeDto {
  @IsEnum(EmailType)
  public type: EmailType;

  @ValidateIf((o) => [EmailType.ACCOUNT_SUCCESS, EmailType.MAIN_INQUIRY].includes(o.type))
  @PostDataMatchesType()
  postData: Record<string, any>;
}

关键说明

  • 避免重复属性:用联合类型(方案一)或通用对象类型(方案二)定义postData,解决TypeScript重复声明报错。
  • 动态匹配逻辑:通过访问ValidationArguments中的object获取当前type值,从而切换对应的验证规则或转换目标。
  • 验证逻辑复用:直接复用已有DTO的验证规则,无需重复编写校验逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 18:24:54