如何在NestJS DTO中实现两字段二选一必填并配置正确Swagger文档
phone/email二选一必填+Swagger文档同步生成落地方案
你原有实现存在三个问题:一是第二个字段笔误重复写了phone,二是仅靠字段级@ValidateIf无法兜底两个字段都不传的场景,三是默认Swagger生成逻辑无法识别条件必填规则,以下是可直接落地的实现方案:
实现思路
整体逻辑做两层对齐:校验层用class-validator实现字段级+类级双层校验,Swagger层用OAS3原生oneOf关键字定义互斥必填规则,两边规则独立但标准统一,不会出现文档和实际校验不一致的问题。
第一步:编写校验逻辑
先写类级自定义校验器兜底二选一规则,再补全字段级校验,修正原有笔误:
import { IsString, ValidateIf, IsNotEmpty, Validate, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; // 类级校验器:兜底校验phone/email至少传一个 @ValidatorConstraint({ name: 'phoneOrEmailRequired', async: false }) class PhoneOrEmailRequired implements ValidatorConstraintInterface { validate(_: any, args: ValidationArguments) { const target = args.object as Record<string, any>; return Boolean(target.phone) || Boolean(target.email); } defaultMessage() { return 'phone和email必须传入其中一个'; } } // 公共业务字段可抽离为基类,避免重复编写 class MyBaseDto { // 其他业务字段正常编写校验和ApiProperty即可 @ApiProperty({ description: '用户昵称' }) @IsString() nickname: string; // 其余业务字段... } // 最终入参DTO @Validate(PhoneOrEmailRequired) export class MyDto extends MyBaseDto { @ApiProperty({ description: '手机号', required: false }) @IsString() @IsNotEmpty() // 当email未传时,当前字段必填 @ValidateIf(obj => !obj.email) phone?: string; @ApiProperty({ description: '邮箱', required: false }) @IsString() @IsNotEmpty() // 当phone未传时,当前字段必填 @ValidateIf(obj => !obj.phone) email?: string; }
第二步:配置Swagger生成oneOf规则
定义两个仅必填字段不同的DTO类型用于文档生成,在接口上通过@ApiBody指定oneOf引用,Swagger会自动生成符合OAS3规范的互斥结构:
import { ApiBody, getSchemaPath } from '@nestjs/swagger'; import { Body, Controller, Post } from '@nestjs/common'; import { MyDto } from './dto'; // 两种互斥请求结构,仅用于Swagger文档生成 class DtoWithPhone extends MyDto { @ApiProperty({ required: true }) phone: string; @ApiProperty({ required: false }) email?: string; } class DtoWithEmail extends MyDto { @ApiProperty({ required: false }) phone?: string; @ApiProperty({ required: true }) email: string; } @Controller('user') export class UserController { @Post('create') @ApiBody({ schema: { oneOf: [ { $ref: getSchemaPath(DtoWithPhone) }, { $ref: getSchemaPath(DtoWithEmail) } ] } }) create(@Body() dto: MyDto) { // 业务逻辑处理 } }
效果说明
- 校验层:字段级校验保证传入字段格式正确,类级校验兜底二选一规则,既不会出现两个字段都不传的情况,也不会强制要求两个都传,空字符串、null等无效值会被正确拦截
- Swagger层:生成的文档会自动展示两种可选请求结构,接口调试时会自动提示对应必填字段,完全符合OAS3规范,不会出现多余字段或错误的必填标记
如果使用v6以上版本的@nestjs/swagger,也可以直接在DTO类上加@ApiSchema({ oneOf: [{required: ['phone']}, {required: ['email']}] })简化配置,不过拆分类的方式兼容性最好,所有版本都能正常生成文档。
内容的提问来源于stack exchange,提问作者Abdulla Qurbonov
相关产品推荐
相关产品推荐

