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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:01:41