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
相关产品推荐
相关产品推荐

