class-validator和class-transformer
用于 Typescript 或 ES6+的类验证,基于validator.js
class-validator 中文文档
安装
npm install class-validator --save
基本使用
创建一个Post
作为演示,在每个属性上添加不同的验证装饰器尝试
import { validate, validateOrReject, Contains, IsInt, Length, IsEmail, IsFQDN, IsDate, Min, Max } from 'class-validator';
export class Post {
@Length(10, 20)
title: string;
@Contains('hello')
text: string;
@IsInt()
@Min(0)
@Max(10)
rating: number;
@IsEmail()
email: string;
@IsFQDN()
site: string;
@IsDate()
createDate: Date;
}
let post = new Post();
post.title = 'Hello'; // should not pass
post.text = 'this is a great post about hell world'; // should not pass
post.rating = 11; // should not pass
post.email = 'google.com'; // should not pass
post.site = 'googlecom'; // should not pass
// 如果验证失败不会停止运行程序
validate(post).then((errors) => {
if (errors.length > 0) {
console.log('validation failed. errors: ', errors);
} else {
console.log('validation succeed');
}
});
// 验证失败就停止运行程序
validateOrReject(post).catch((errors) => {
console.log('Promise rejected (validation failed). Errors: ', errors);
});
// 或者
async function validateOrRejectExample(input) {
try {
await validateOrReject(input);
} catch (errors) {
console.log('Caught promise rejection (validation failed). Errors: ', errors);
}
}
选项
validate
函数的第二个参数是一个选项对象,尽量设置forbidNonWhitelisted
为true
以避免 unkown 对象的输入验证
export interface ValidatorOptions {
skipMissingProperties?: boolean;
whitelist?: boolean;
forbidNonWhitelisted?: boolean;
groups?: string[];
dismissDefaultMessages?: boolean;
validationError?: {
target?: boolean,
value?: boolean,
};
forbidUnknownValues?: boolean;
}
验证错误
验证失败返回的错误数组是ValidationError
类的对象的数组,格式如下
{
target: Object; // Object that was validated.
property: string; // Object's property that haven't pass validation.
value: any; // Value that haven't pass a validation.
constraints?: { // Constraints that failed validation with error messages.
[type: string]: string;
};
children?: ValidationError[]; // Contains all nested validation errors of the property
}
返回的格式如下
[{
target: /* post object */,
property: "title",
value: "Hello",
constraints: {
length: "$property must be longer than or equal to 10 characters"
}
}, {
target: /* post object */,
property: "text",
value: "this is a great post about hell world",
constraints: {
contains: "text must contain a hello string"
}
},
// and other errors
]
在 http 响应中我们一般不想在错误中暴露target
,那么就可以如下方式禁用它
validator.validate(post, { validationError: { target: false } });
验证消息
我们可以自定义在ValidationError
对象中返回的错误消息
import { MinLength, MaxLength } from 'class-validator';
export class Post {
@MinLength(10, {
message: 'Title is too short',
})
@MaxLength(50, {
message: 'Title is too long',
})
title: string;
}
消息可以接受几个参数作为变量,用字符串混合的方式放入,比如"$constraint1 characters"
import { MinLength, MaxLength } from 'class-validator';
export class Post {
@MinLength(10, {
// here, $constraint1 will be replaced with "10", and $value with actual supplied value
message: 'Title is too short. Minimal length is $constraint1 characters, but actual is $value',
})
@MaxLength(50, {
// here, $constraint1 will be replaced with "50", and $value with actual supplied value
message: 'Title is too long. Maximal length is $constraint1 characters, but actual is $value',
})
title: string;
}
能接受的变量如下
value
- 被验证的值constraints
- 由指定验证类型定义的约束数组targetName
- 验证对象的类的名称object
- 被验证的对象property
- 被验证的属性名
当然message
还可以接受一个函数的返回值,这个函数的参数为ValidationArguments
类的对象,而ValidationArguments
类的属性就是上面的变量列表
import { MinLength, MaxLength, ValidationArguments } from 'class-validator';
export class Post {
@MinLength(10, {
message: (args: ValidationArguments) => {
if (args.value.length === 1) {
return 'Too short, minimum length is 1 character';
} else {
return 'Too short, minimum length is ' + args.constraints[0] + ' characters';
}
},
})
title: string;
}
特殊类型
class-validator
对一些经常使用的特殊类型有专门的处理方法
集合类型
验证数组,Sets
,Map
等集合类型需要开启each
选项
验证数组
import { MinLength, MaxLength } from 'class-validator';
export class Post {
@MaxLength(20, {
each: true,
})
tags: string[];
}
验证 Sets
import { MinLength, MaxLength } from 'class-validator';
export class Post {
@MaxLength(20, {
each: true,
})
tags: Set<string>;
}
验证 Map
import { MinLength, MaxLength } from 'class-validator';
export class Post {
@MaxLength(20, {
each: true,
})
tags: Map<string, string>;
}
嵌套对象
一个验证的类中的某些属性可能是类一个的对象,比如Post
类的user
属性为User
类,则可以使用@ValidateNested()
方式来同时验证Post
和嵌入的User
类
import { ValidateNested } from 'class-validator';
export class Post {
@ValidateNested()
user: User;
}
Promise 对象
如果待验证的属性是一个Promise
对象,比如通过await
关键字返回的值,则可以使用@ValidatePromise()
import { ValidatePromise, Min } from 'class-validator';
export class Post {
@Min(0)
@ValidatePromise()
userId: Promise<number>;
}
@ValidatePromise()
也可以和@ValidateNested()
一起使用
import { ValidateNested, ValidatePromise } from 'class-validator';
export class Post {
@ValidateNested()
@ValidatePromise()
user: Promise<User>;
}
高级主题
子类验证
如果定义一个从另一个继承的子类时,子类将自动继承父级的装饰器。如果在后代类中重新定义了属性,则装饰器将从该类和基类中继承
import {validate} from "class-validator";
class BaseContent {
@IsEmail()
email: string;
@IsString()
password: string;
}
class User extends BaseContent {
@MinLength(10)
@MaxLength(20)
name: string;
@Contains("hello")
welcome: string;
@MinLength(20)
password: string; /
}
let user = new User();
user.email = "invalid email"; // inherited property
user.password = "too short" // password wil be validated not only against IsString, but against MinLength as well
user.name = "not valid";
user.welcome = "helo";
validate(user).then(errors => {
// ...
}); // it will return errors for email, title and text properties
条件验证
当某个属性需要满足一定条件验证时可以使用(@ValidateIf
)装饰器
import { ValidateIf, IsNotEmpty } from 'class-validator';
export class Post {
otherProperty: string;
@ValidateIf((o) => o.otherProperty === 'value')
@IsNotEmpty()
example: string;
}
白名单
一个被验证的类的对象可以定义在类中不存在的属性,在验证时不会产生错误。为了使只有添加了验证装饰器的属性才能被定义,你需要把whitelist
设置为true
,那么如果对象中定义一个类中不存在的属性就无法通过验证了。
import { validate } from 'class-validator';
// ...
validate(post, { whitelist: true });
开启白名单之后所有没有加上验证装饰器的属性被定义后都将无法通过验证,如果你想一些属性可以被定义但是又不想被验证,如果条件验证中的otherProperty
属性,那么你需要在该属性上面添加一个@Allow
装饰器
/**
* title可以被定义
* nonWhitelistedProperty不能被定义,否则验证失败
*/
import {validate, Allow, Min} from "class-validator";
export class Post {
@Allow()
title: string;
@Min(0)
views: number;
nonWhitelistedProperty: number;
}
let post = new Post();
post.title = 'Hello world!';
post.views = 420;
post.nonWhitelistedProperty = 69;
// 额外属性不能被添加,否则验证失败
(post as any).anotherNonWhitelistedProperty = "something";
validate(post).then(errors => {
// post.nonWhitelistedProperty is not defined
// (post as any).anotherNonWhitelistedProperty is not defined
...
});
如果你想要所有没有添加验证装饰器的属性 都无法定义,则可以设置forbidNonWhitelisted
为true
这个一般不要设置,否则属性添加@Allow 会都没用了
import { validate } from 'class-validator';
// ...
validate(post, { whitelist: true, forbidNonWhitelisted: true });
添加上下文
你可以在验证装饰其中添加一个自定义的上下文对象,此对象在验证失败时被ValidationError
的实例获取
import { validate } from 'class-validator';
class MyClass {
@MinLength(32, {
message: 'EIC code must be at least 32 characters',
context: {
errorCode: 1003,
developerNote: 'The validated string must contain 32 or more characters.',
},
})
eicCode: string;
}
const model = new MyClass();
validate(model).then((errors) => {
//errors[0].contexts['minLength'].errorCode === 1003
});
跳过缺失属性
有时候你需要跳过一些对象中没有设置的属性,比如更新数据模型时,与创建模型不同的是你只会更新部分值,那么这时候你就需要设置skipMissingProperties
为true
,当然可能一部分属性是你不想被跳过验证的 ,那么需要在这些属性上加上@IsDefined()
装饰器,加了@IsDefined()
装饰器的属性会忽略skipMissingProperties
而必定被验证
import { validate } from 'class-validator';
// ...
validate(post, { skipMissingProperties: true });
验证组
import { validate, Min, Length } from 'class-validator';
export class User {
@Min(12, {
groups: ['registration'],
})
age: number;
@Length(2, 20, {
groups: ['registration', 'admin'],
})
name: string;
}
let user = new User();
user.age = 10;
user.name = 'Alex';
validate(user, {
groups: ['registration'],
}); // 无法通过验证
validate(user, {
groups: ['admin'],
}); // 可以通过验证
validate(user, {
groups: ['registration', 'admin'],
}); // 无法通过验证
validate(user, {
groups: undefined, // 默认模式
}); // 无法通过验证,因为没有指定group则所有属性都将被验证
validate(user, {
groups: [],
}); // 无法通过验证 (与'groups: undefined'相同)
在验证中还有一个always: true
选项,如果添加了此选项,无论验证时设定的是哪种模式的groups
,都将被验证
使用服务容器
你可以使用服务容器来加载验证器通过依赖注入的方式使用。以下如何将其与typedi集成的示例:
import { Container } from 'typedi';
import { useContainer, Validator } from 'class-validator';
// do this somewhere in the global application level:
useContainer(Container);
let validator = Container.get(Validator);
// now everywhere you can inject Validator class which will go from the container
// also you can inject classes using constructor injection into your custom ValidatorConstraint-s
非装饰器验证
如果你的运行环境不支持装饰器请看这里
验证普通对象
Nest.js 中使用的验证管道就是 class-validator+class-transformer 结合的方式
由于装饰器的性质,必须使用new class()
语法实例化待验证的对象。如果你使用了 class-validator 装饰器定义了类,并且想要验证普通的 JS 对象(文本对象或 JSON.parse 返回),则需要将其转换为类实例
(例如,使用class-transformer或仅使用class-transformer-validator扩展可以为您完成此任务。
自定义验证
自定义规则类
你可以创建一个自定义的验证规则的类,并在规则类上添加@ValidatorConstraint
装饰器。 还可以设置验证约束名称(name
选项)-该名称将在ValidationError
中用作“error type”。 如果您不提供约束名称,它将自动生成。
规则类必须实现ValidatorConstraintInterface
接口及validate
方法,该接口定义了验证逻辑。 如果验证成功,则方法返回true
,否则返回false
。 自定义验证器可以是异步的,如果您想在执行一些异步操作后执行验证,只需在validate
方法中返回带有布尔值的promise
。
我们还可以定义了可选方法defaultMessage
,它在属性上的装饰器未设置错误消息的情况下定义了默认错误消息。
首选我们创建一个CustomTextLength
演示用的验证规则类
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
@ValidatorConstraint({ name: 'customText', async: false })
export class CustomTextLength implements ValidatorConstraintInterface {
validate(text: string, args: ValidationArguments) {
return text.length > 1 && text.length < 10; // 对于异步验证,您必须在此处返回Promise<boolean>
}
defaultMessage(args: ValidationArguments) {
// 如果验证失败,您可以在此处提供默认错误消息
return 'Text ($value) is too short or too long!';
}
}
定义好规则后我们就可以在类中使用了
import { Validate } from 'class-validator';
import { CustomTextLength } from './CustomTextLength';
class Post {
@Validate(CustomTextLength, {
message: 'Title is too short or long!',
})
title: string;
}
validate(post).then((errors) => {
// ...
});
你也可以将自定义的约束传入规则类,并通过约束来设定验证的条件
import { Validate } from 'class-validator';
import { CustomTextLength } from './CustomTextLength';
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
@ValidatorConstraint()
class CustomTextLength implements ValidatorConstraintInterface {
validate(text: string, validationArguments: ValidationArguments) {
return text.length > validationArguments.constraints[0] && text.length < validationArguments.constraints[1];
}
}
class Post {
@Validate(CustomTextLength, [3, 20], {
message: 'Wrong post title',
})
title: string;
}