class-validator 验证的5个开关:如何让错误只出现在该出现的地方
class-validator 验证的5个开关:如何让错误只出现在该出现的地方
【免费下载链接】class-validatorDecorator-based property validation for classes.项目地址: https://gitcode.com/gh_mirrors/cl/class-validator
更新用户资料的接口,有时 avatar 传空整个请求就被打回,有时又希望它放行——问题多半不在装饰器,而在validate()没传第二个参数。class-validator 的验证行为可以由 ValidatorOptions 里的5个开关定制,返回的错误则统一装在ValidationError结构里。读完这篇,你能按接口场景自由组合"哪些字段要验、验不过返回什么"。
10秒看懂:开关管什么,不管什么
ValidatorOptions管验证"怎么跑",装饰器的ValidationOptions(message、groups、each)管单个字段"怎么验",两层各管各的:
| 类别 | 选项 | 一句话效果 |
|---|---|---|
| 跳过缺失值 | skipUndefinedProperties/skipNullProperties/skipMissingProperties | 值为 undefined、null 或两者之一时,整个字段不参与验证 |
| 字段白名单 | whitelist/forbidNonWhitelisted | 未加装饰器的属性被删除或触发错误 |
| 错误输出 | validationError.target/value、stopAtFirstError | 控制错误里带不带原对象、遇错是否停 |
| 消息控制 | dismissDefaultMessages、enableDebugMessages | 默认消息是否保留、是否打调试警告 |
它不做的:类型转换(JSON 变类实例要配 class-transformer)、跨字段一致性校验(需写自定义约束)。
缺失值怎么处理:三个跳过开关
class-validator 把undefined(字段没赋值)和null(显式传 null)当成两种缺失,分别给了开关,skipMissingProperties是两者的并集。
原理就一行:ValidationExecutor在跑任何验证器前先判值,命中开关直接return,该字段后续验证全部跳过(见 ValidationExecutor.ts 的performValidations)。注意@IsDefined会绕开这三个开关,声明"此字段必须存在"时用它在字段上标,而不是靠跳过开关放行。
这段代码演示同一份数据在三种开关下的表现:
class User { @IsEmail() email: string; @IsNotEmpty() nickname: string; @IsUrl() avatar: string; } const user: any = new User(); user.email = 'a@b.com'; user.avatar = null; // 显式 null validate(user).then(e => console.log(e.map(x => x.property))); // ['nickname', 'avatar'] validate(user, { skipMissingProperties: true }).then(e => console.log(e.map(x => x.property))); // 只剩 ['nickname']更新接口的典型配法就是skipMissingProperties: true,让未传的可选字段安静通过。这里要注意:skipMissingProperties: true只挡 null 和 undefined,空字符串照样进验证器。
whitelist 和 forbidNonWhitelisted:外来字段怎么办
两个开关控制"没加任何装饰器的属性"的命运。
配置写法:{ whitelist: true };实际效果分两支:没开forbidNonWhitelisted时,这类属性被直接从对象上delete;开了则每个属性生成一条constraints里带whitelist的错误,消息为property xxx should not exist,原对象保持原样。
适用场景是防前端多传参数。源码 ValidationExecutor.ts 的whitelist方法会先扫一遍对象所有 key,没有对应元数据的进黑名单,然后按上面两支处理。
这段代码演示两种模式的差异,注意验证后draft.hackerField的变化:
class Post { @IsString() title: string; @Min(0) rating: number; } const draft: any = new Post(); draft.title = 'hi'; draft.hackerField = 'extra'; await validate(draft, { whitelist: true }); console.log(draft.hackerField); // undefined,已被 delete const draft2: any = { ...draftRaw }; await validate(draft2, { whitelist: true, forbidNonWhitelisted: true }); // 返回1条错误,原对象不被改动这里要注意:whitelist是真删字段,不是复制一份再删。想保留原始请求用于日志时,先Object.assign({}, obj)浅拷贝再验。如果某个字段只是透传、不需要验证,给它标@Allow()就能留档,不必硬塞一个无意义的验证器。
把错误说成人话:ValidationError 结构
验证结果是一个ValidationError[],每个节点就是一棵树的分支:property是属性名,value是出错时的值,constraints是{ 验证器名: 消息 },嵌套对象的错误挂在children数组里递归展开(定义见 ValidationError.ts)。
控制"带什么出去"用validationError子选项:target和value默认为 true,生产环境建议设validationError: { target: false },避免整个对象混进错误响应造成 JSON 循环引用。
toString()支持4个参数:是否加 ANSI 高亮、是否为子错误、父路径、显示约束名还是约束消息。默认输出长这样:
An instance of Post has failed the validation: - property title has failed the following constraints: isString - property tags[1] has failed the following constraints: length数组下标会被格式化成[1]这种形式,方便直接定位元素,这在做批量导入校验日志时特别省事。
最小可复现:一个更新接口够用的配置
下面这段只配了一个开关,模拟"可选字段允许缺省"的更新场景:
import { IsString, Length } from 'class-validator'; import { validate } from 'class-validator'; class Post { @IsString() @Length(10, 50) title: string; } const post: any = new Post(); post.title = 'hello'; // 不足10位 validate(post).then(e => console.log(e.length)); // 1:title 报 length validate(post, { stopAtFirstError: true }).then(e => console.log(Object.keys(e[0].constraints)) ); // ['isString']:同一字段后续约束不再跑为什么这么写:错误收集是"每字段先建节点,全部验证跑完再删空节点"(stripEmptyErrors),所以默认会拿到字段的全部违规;stopAtFirstError: true让每个字段只报第一条约束错误,前端表单只需要一句提示时正好省掉噪音。记住选项是每次validate()调用生效的全局配置,不同接口该传不同的 options 对象,别追求一份配置打天下。
避坑清单
skipMissingProperties没放行空字符串:''不是 null/undefined,仍会进验证器 → 根因是开关只判null === value || undefined === value→ 解法:字段上补@IsOptional()处理空值语义,或显式把空串归一化成 undefined 再验。- 验证完发现请求体里多了个字段消失了:
whitelist: true直接delete原对象上的属性 → 解法:先浅拷贝再验证,或者改用forbidNonWhitelisted: true让多传字段显式报错而不是静默蒸发。 - 传了
groups才生效,不传就全过:strictGroups未开启时,不传groups的验证会忽略所有带组的装饰器,表现像"规则失效" → 解法:按默认行为给所有验证器补无组配置,或接口层强制要求groups参数,必要时开strictGroups: true反转默认行为。 - 错误列表莫名变短:
dismissDefaultMessages: true后,未写自定义message的约束产出undefined消息,整个错误节点被stripEmptyErrors过滤 → 解法:要么给装饰器补message,要么别开这个开关。 validate({})直接失败:forbidUnknownValues默认为 true,对没有任何装饰器元数据的裸对象会直接报unknownValue错误 → 解法:验普通 JSON 对象时显式传forbidUnknownValues: false,或先把对象转成带装饰器的类实例。
class-validator 只负责"判",不负责"转":入参是原始 JSON 时仍需 class-transformer 先转成类实例,跨字段一致性逻辑则要落在自定义约束里。想继续深入,可以看 whitelist-validation.spec.ts 里白名单三种行为的完整用例,以及 ValidationExecutor.ts 中execute主流程如何串联这些开关。
【免费下载链接】class-validatorDecorator-based property validation for classes.项目地址: https://gitcode.com/gh_mirrors/cl/class-validator
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考