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

如何实现DTO中周期价格属性的可选性与合法性验证?

问题:NestJS DTO验证规则不符合预期

现有如下DTO定义:

export class UpsertSubscription {
  @Expose()
  @ValidateIf((o) => !o.monthly && !o.yearly)
  @IsNumber({}, { message: 'tiers.priceMustBeANumber' })
  @Min(0, { message: 'tiers.priceMustBeAtLeastZero' })
  public daily?: number;

  @Expose()
  @ValidateIf((o) => !o.daily && !o.yearly)
  @IsNumber({}, { message: 'tiers.priceMustBeANumber' })
  @Min(0, { message: 'tiers.priceMustBeAtLeastZero' })
  public monthly?: number;

  @Expose()
  @ValidateIf((o) => !o.daily && !o.monthly)
  @IsNumber({}, { message: 'tiers.priceMustBeANumber' })
  @Min(0, { message: 'tiers.priceMustBeAtLeastZero' })
  public yearly?: number;
}

原本期望通过ValidateIf实现:当另外两个属性中的任意一个已定义时,当前属性为可选;但实际情况是,比如设置daily=10时,monthly可以传入任意非法值(如"something different"),验证不会触发,不符合预期。需求是:当其中一个属性已定义时,其余属性为可选,但如果设置了就必须符合验证规则。


解决方案

问题根源

原来的ValidateIf逻辑是仅当另外两个属性都未定义时,才执行当前属性的验证。这就导致只要其他属性存在,当前属性的验证会被直接跳过,非法值也能通过校验,和预期完全相反。

修正属性级验证

调整每个属性的ValidateIf条件,改为只要当前属性被设置(有值),就执行验证;未设置则跳过(保持可选):

export class UpsertSubscription {
  @Expose()
  @ValidateIf((o) => o.daily !== undefined)
  @IsNumber({}, { message: 'tiers.priceMustBeANumber' })
  @Min(0, { message: 'tiers.priceMustBeAtLeastZero' })
  public daily?: number;

  @Expose()
  @ValidateIf((o) => o.monthly !== undefined)
  @IsNumber({}, { message: 'tiers.priceMustBeANumber' })
  @Min(0, { message: 'tiers.priceMustBeAtLeastZero' })
  public monthly?: number;

  @Expose()
  @ValidateIf((o) => o.yearly !== undefined)
  @IsNumber({}, { message: 'tiers.priceMustBeANumber' })
  @Min(0, { message: 'tiers.priceMustBeAtLeastZero' })
  public yearly?: number;
}

这样修改后,无论其他属性是否存在,只要当前属性被赋值,就会触发IsNumber和Min验证,非法值会被拦截。

可选:强制至少一个属性必填

如果需要确保请求中至少传入一个价格属性(daily/monthly/yearly中的一个),可以添加类级别的自定义验证:

  1. 定义验证约束:
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';

@ValidatorConstraint({ name: 'atLeastOnePrice', async: false })
export class AtLeastOnePrice implements ValidatorConstraintInterface {
  validate(obj: UpsertSubscription) {
    return obj.daily !== undefined || obj.monthly !== undefined || obj.yearly !== undefined;
  }

  defaultMessage() {
    return 'tiers.atLeastOnePriceRequired';
  }
}
  1. 在DTO类上添加验证装饰器:
import { Validate } from 'class-validator';

@Validate(AtLeastOnePrice)
export class UpsertSubscription {
  // ... 原有属性定义
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 21:54:53