NestJS中ValidateIf无法实现条件字段验证的问题咨询
问题
我正在使用NestJS开发一款会计应用,定义了CreateTransactionDto,希望根据transactionType的值控制字段的必填或禁用规则:
- 当
transactionType为SALE时,需提供quantity、unitPrice字段,但禁止传入receivedAmount; - 当
transactionType为PAYMENT时,需必填receivedAmount,禁止传入quantity类字段。
我对receivedAmount字段的验证定义如下:
// dto @ApiProperty({ description: 'Type of the transaction', enum: TransactionType, }) @IsNotEmpty() @IsEnum(TransactionType) transactionType: TransactionType; @ValidateIf((o: CreateTransactionDto) => o.transactionType === TransactionType.PAYMENT) @IsNotEmpty({ message: 'Received amount is required for PAYMENT transactions', }) @IsNumber() @ValidateIf((o: CreateTransactionDto) => o.transactionType === TransactionType.SALE) @IsEmpty({ message: 'Received amount should not be provided for SALE transactions', }) receivedAmount?: number;
同时配置了全局ValidationPipe:
// main.ts app.useGlobalPipes( new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true, forbidUnknownValues: true, stopAtFirstError: false, }), );
但发送如下JSON请求体时,未触发任何验证错误:
{ "customerId": 1, "transactionType": "SALE", "transactionDate": "2023-10-01", "description": "Sale of goods", "quantity": 10, "quantityUnit": "pcs", "unitPrice": 50, "receivedAmount": 500 }
尽管transactionType为SALE且receivedAmount不应被传入,请求仍正常通过。请问如何正确使用ValidateIf根据其他字段值实现条件验证或字段拦截?是否存在配置错误或遗漏?
解决方案
问题核心在于多个ValidateIf装饰器的逻辑冲突,以及IsEmpty的使用场景限制。以下是具体修正方案:
1. 使用自定义验证器统一处理逻辑(推荐)
多个ValidateIf会导致验证规则被拆分,当第一个条件不满足时,后续验证会直接跳过。自定义验证器可以统一处理两种场景的规则:
步骤1:创建自定义验证器
// transaction.validator.ts import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; import { TransactionType } from './transaction.enum'; @ValidatorConstraint({ name: 'receivedAmountValidation', async: false }) export class ReceivedAmountValidation implements ValidatorConstraintInterface { validate(receivedAmount: number, args: ValidationArguments) { const { transactionType } = args.object as { transactionType: TransactionType }; if (transactionType === TransactionType.SALE) { // SALE类型时,receivedAmount必须不存在 return receivedAmount === undefined; } else if (transactionType === TransactionType.PAYMENT) { // PAYMENT类型时,receivedAmount必须是有效正数 return typeof receivedAmount === 'number' && !isNaN(receivedAmount) && receivedAmount > 0; } return true; } defaultMessage(args: ValidationArguments) { const { transactionType } = args.object as { transactionType: TransactionType }; if (transactionType === TransactionType.SALE) { return 'Received amount should not be provided for SALE transactions'; } else if (transactionType === TransactionType.PAYMENT) { return 'Received amount is required and must be a positive number for PAYMENT transactions'; } return 'Invalid received amount'; } }
步骤2:在Dto中应用自定义验证器
// dto import { Validate, ValidateIf, IsNotEmpty, IsEmpty, IsNumber } from 'class-validator'; import { ReceivedAmountValidation } from './transaction.validator'; @ApiProperty({ description: 'Type of the transaction', enum: TransactionType, }) @IsNotEmpty() @IsEnum(TransactionType) transactionType: TransactionType; // 统一验证receivedAmount @Validate(ReceivedAmountValidation) receivedAmount?: number; // SALE类型必填字段验证 @ValidateIf(o => o.transactionType === TransactionType.SALE) @IsNotEmpty({ message: 'Quantity is required for SALE transactions' }) quantity?: number; @ValidateIf(o => o.transactionType === TransactionType.SALE) @IsNotEmpty({ message: 'Unit price is required for SALE transactions' }) @IsNumber() unitPrice?: number; // PAYMENT类型禁止字段验证 @ValidateIf(o => o.transactionType === TransactionType.PAYMENT) @IsEmpty({ message: 'Quantity should not be provided for PAYMENT transactions' }) quantity?: number; @ValidateIf(o => o.transactionType === TransactionType.PAYMENT) @IsEmpty({ message: 'Unit price should not be provided for PAYMENT transactions' }) unitPrice?: number;
2. 修复原有ValidateIf逻辑(不推荐)
如果坚持使用原生装饰器,需要合并ValidateIf条件,确保两种场景的验证都能触发:
@ValidateIf((o: CreateTransactionDto) => o.transactionType === TransactionType.PAYMENT || o.transactionType === TransactionType.SALE ) @IsNotEmpty({ message: 'Received amount is required for PAYMENT transactions', groups: [TransactionType.PAYMENT] }) @IsEmpty({ message: 'Received amount should not be provided for SALE transactions', groups: [TransactionType.SALE] }) @IsNumber({ groups: [TransactionType.PAYMENT] }) receivedAmount?: number;
这种方式需要在ValidationPipe中启用分组验证,根据transactionType动态切换验证组,复杂度较高,不如自定义验证器直观。
3. 关于全局ValidationPipe的说明
你配置的whitelist: true和forbidNonWhitelisted: true仅对Dto中未定义的字段生效。receivedAmount已在Dto中声明,因此即使不符合业务规则,也不会被这两个配置拦截,必须通过验证器限制。
内容的提问来源于stack exchange,提问作者muratmutlu
相关产品推荐
相关产品推荐

