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

NestJS中Zod验证管道误校验所有Query参数问题

问题:Zod验证管道误校验所有Query参数,而非指定参数

我基于Zod实现了验证管道,但遇到一个问题:当控制器包含多个@Query参数时,Zod验证管道会校验所有Query参数,而不是仅校验被该管道装饰的参数。比如示例里,只有skip、take需要校验,但comments、demandes也被传入了验证逻辑,导致strict模式下报错。在管道的transform函数中打印value时,能看到4个Query参数都被传进来了,这不符合预期。

控制器中的管道使用

@Get('/tickets')
async getTickets(
  @Query(new ZodValidationPipe(paginationSchema, 'tickets'))
  pagination: TPaginationDTO,
  @Query('comments', new DefaultValuePipe(false), ParseBoolPipe)
  withComments: boolean = false,
  @Query('demandes', new DefaultValuePipe(false), ParseBoolPipe)
  withDemandes: boolean = false,
)

验证管道代码

export class ZodValidationPipe implements PipeTransform {
  constructor(
    private readonly zodSchema: ZodSchema,
    private readonly source: string = '',
  ) {}
  transform(value: any) {
    const parsedValue = zodSchema.safeParse(value);
    if (parsedValue.error) {
      throw new BadRequestException();
    } else {
      return parsedValue.data;
    }
  }
}

TPaginationDTO Schema定义

const paginationSchema = z
  .object({
    skip: zodInputStringToNumberPipe(
      z.number().positive().default(0),
    ).optional(),
    take: zodInputStringToNumberPipe(
      z
        .number()
        .positive()
        .default(Number(process.env.DEFAULT_PAGINATION_TAKE)),
    ).optional(),
  })
  .strict();

type TPaginationDTO = z.infer<typeof paginationSchema>;

测试场景

  • 调用URL:http://localhost:3000/app/tickets?take=9&comments=true&demandes=true
  • 错误信息:
" - unrecognized_keys - Unrecognized key(s) in object: 'comments', 'demandes' "
  • transform函数接收的value值:
"{\"take\":\"9\",\"comments\":\"true\",\"demandes\":\"true\"}"

解决方案

方案1:修改管道,仅提取Schema定义的字段校验

问题根源是当@Query不指定具体参数名时,NestJS会把整个Query对象传给管道。所以我们需要在验证前,先从传入的value中过滤出Schema里定义的字段,再进行校验。修改后的管道代码如下:

export class ZodValidationPipe implements PipeTransform {
  constructor(
    private readonly zodSchema: ZodSchema,
    private readonly source: string = '',
  ) {}
  transform(value: any) {
    // 只保留Schema中定义的字段
    const targetFields = Object.keys(this.zodSchema.shape);
    const filteredValue = Object.fromEntries(
      Object.entries(value).filter(([key]) => targetFields.includes(key))
    );

    const parsedValue = this.zodSchema.safeParse(filteredValue);
    if (parsedValue.error) {
      throw new BadRequestException(parsedValue.error.errors);
    } else {
      return parsedValue.data;
    }
  }
}

这样处理后,管道只会校验skip和take字段,忽略其他Query参数,既保留了Schema的strict模式,又符合预期行为。

方案2:移除Schema的.strict()(不推荐)

如果不需要严格校验未定义字段,可以去掉.strict(),这样Zod会忽略未定义的键。但这会失去严格校验的能力,不推荐使用。

方案3:拆分@Query参数(不推荐)

把pagination拆成两个单独的@Query参数,分别绑定管道:

@Get('/tickets')
async getTickets(
  @Query('skip', new ZodValidationPipe(z.number().positive().default(0)))
  skip: number = 0,
  @Query('take', new ZodValidationPipe(z.number().positive().default(Number(process.env.DEFAULT_PAGINATION_TAKE))))
  take: number,
  @Query('comments', new DefaultValuePipe(false), ParseBoolPipe)
  withComments: boolean = false,
  @Query('demandes', new DefaultValuePipe(false), ParseBoolPipe)
  withDemandes: boolean = false,
)

但这样会失去DTO聚合的优势,代码冗余,不推荐。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 04:42:20