> 随着时间的推移,可能部分内容无法与官方最新版本同步,请自行对比查看,我有空会更新 用于Typescript或ES6+的类验证,基于[validator.js](https://github.com/chriso/validator.js) [手动验证方法列表](https://github.com/typestack/class-validator#manual-validation)和[验证装饰器列表](https://github.com/typestack/class-validator#validation-decorators) ```python print("Hello, World!") ``` ## class-validator中文文档 ### 安装 ```shell npm install class-validator --save ``` #### 基本使用 创建一个`Post`作为演示,在每个属性上添加不同的验证[装饰器](https://test.3rcd.com/notes/decorator.html)尝试 ```javascript 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对象的输入验证 ```javascript export interface ValidatorOptions { skipMissingProperties?: boolean; whitelist?: boolean; forbidNonWhitelisted?: boolean; groups?: string[]; dismissDefaultMessages?: boolean; validationError?: { target?: boolean; value?: boolean; }; forbidUnknownValues?: boolean; } ``` #### 验证错误 验证失败返回的错误数组是`ValidationError`类的对象的数组,格式如下 ```javascript { 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 } ``` 返回的格式如下 ```javascript [{ 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`,那么就可以如下方式禁用它 ```javascript validator.validate(post, { validationError: { target: false } }); ``` #### 验证消息 我们可以自定义在`ValidationError`对象中返回的错误消息 ```javascript 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"` ```javascript 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`类的属性就是上面的变量列表 ```javascript 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`选项 #### 验证数组 ```javascript import {MinLength, MaxLength} from "class-validator"; export class Post { @MaxLength(20, { each: true }) tags: string[]; } ``` #### 验证Sets ```javascript import {MinLength, MaxLength} from "class-validator"; export class Post { @MaxLength(20, { each: true }) tags: Set; } ``` #### 验证Map ```javascript import {MinLength, MaxLength} from "class-validator"; export class Post { @MaxLength(20, { each: true }) tags: Map; } ``` #### 嵌套对象 一个验证的类中的某些属性可能是类一个的对象,比如`Post`类的`user`属性为`User`类,则可以使用`@ValidateNested()`方式来同时验证`Post`和嵌入的`User`类 ```javascript import {ValidateNested} from "class-validator"; export class Post { @ValidateNested() user: User; } ``` #### Promise对象 如果待验证的属性是一个`Promise`对象,比如通过`await`关键字返回的值,则可以使用`@ValidatePromise()` ```javascript import {ValidatePromise, Min} from "class-validator"; export class Post { @Min(0) @ValidatePromise() userId: Promise; } ``` `@ValidatePromise()`也可以和`@ValidateNested()`一起使用 ```javascript import {ValidateNested, ValidatePromise} from "class-validator"; export class Post { @ValidateNested() @ValidatePromise() user: Promise; } ``` ### 高级主题 #### 子类验证 如果定义一个从另一个继承的子类时,子类将自动继承父级的装饰器。如果在后代类中重新定义了属性,则装饰器将从该类和基类中继承 ```javascript 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`)装饰器 ```javascript import {ValidateIf, IsNotEmpty} from "class-validator"; export class Post { otherProperty:string; @ValidateIf(o => o.otherProperty === "value") @IsNotEmpty() example:string; } ``` #### 白名单 一个被验证的类的对象可以定义在类中不存在的属性,在验证时不会产生错误。为了使只有添加了**验证装饰器**的属性才能被定义,你需要把`whitelist`设置为`true`,那么如果对象中定义一个类中不存在的属性就无法通过验证了。 ```javascript import {validate} from "class-validator"; // ... validate(post, { whitelist: true }); ``` 开启白名单之后所有没有加上**验证装饰器**的属性被定义后都将无法通过验证,如果你想一些属性可以被定义但是又不想被验证,如果[条件验证](#条件验证)中的`otherProperty`属性,那么你需要在该属性上面添加一个`@Allow`装饰器 ```javascript /** * 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会都没用了 ```javascript import {validate} from "class-validator"; // ... validate(post, { whitelist: true, forbidNonWhitelisted: true }); ``` #### 添加上下文 你可以在验证装饰其中添加一个自定义的上下文对象,此对象在验证失败时被`ValidationError`的实例获取 ```javascript 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`而必定被验证 ```javascript import {validate} from "class-validator"; // ... validate(post, { skipMissingProperties: true }); ``` #### 验证组 ```javascript 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](https://github.com/pleerock/typedi)集成的示例: ```javascript 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 ``` #### 非装饰器验证 如果你的运行环境不支持装饰器请看[这里](https://github.com/typestack/class-validator#defining-validation-schema-without-decorators) #### 验证普通对象 > Nest.js中使用的验证管道就是class-validator+class-transformer结合的方式 由于装饰器的性质,必须使用`new class()`语法实例化待验证的对象。如果你使用了class-validator装饰器定义了类,并且想要验证普通的JS对象(文本对象或JSON.parse返回),则需要将其转换为类实例 (例如,使用[class-transformer](https://github.com/pleerock/class-transformer)或仅使用[class-transformer-validator](https://github.com/19majkel94/class-transformer-validator)扩展可以为您完成此任务。 ### 自定义验证 #### 自定义规则类 你可以创建一个自定义的验证规则的类,并在规则类上添加`@ValidatorConstraint`装饰器。 还可以设置验证约束名称(`name`选项)-该名称将在`ValidationError`中用作“error type”。 如果您不提供约束名称,它将自动生成。 规则类必须实现`ValidatorConstraintInterface`接口及`validate`方法,该接口定义了验证逻辑。 如果验证成功,则方法返回`true`,否则返回`false`。 自定义验证器可以是异步的,如果您想在执行一些异步操作后执行验证,只需在`validate`方法中返回带有布尔值的`promise`。 我们还可以定义了可选方法`defaultMessage`,它在属性上的装饰器未设置错误消息的情况下定义了默认错误消息。 首选我们创建一个`CustomTextLength`演示用的验证规则类 ```javascript 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 } defaultMessage(args: ValidationArguments) { // 如果验证失败,您可以在此处提供默认错误消息 return "Text ($value) is too short or too long!"; } } ``` 定义好规则后我们就可以在类中使用了 ```javascript 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 => { // ... }); ``` 你也可以将自定义的约束传入规则类,并通过约束来设定验证的条件 ```javascript 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; } ``` #### 自定义装饰器 创建自定义装饰器的方法类似创建自定义规则类,只是使用装饰器而已 > 装饰器的详细使用请看我[这篇文章](https://test.3rcd.com/notes/decorator.html) ```javascript import {registerDecorator, ValidationOptions, ValidationArguments} from "class-validator"; function IsLongerThan(property: string, validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ name: "isLongerThan", target: object.constructor, propertyName: propertyName, constraints: [property], options: validationOptions, validator: { validate(value: any, args: ValidationArguments) { const [relatedPropertyName] = args.constraints; const relatedValue = (args.object as any)[relatedPropertyName]; return typeof value === "string" && typeof relatedValue === "string" && value.length > relatedValue.length; // you can return a Promise here as well, if you want to make async validation } } }); }; } export class Post { title: string; @IsLongerThan("title", { /* you can also use additional validation options, like "groups" in your custom validation decorators. "each" is not supported */ message: "Text must be longer than the title" }) text: string; } ``` 在自定义装饰器上仍然可以使用`ValidationConstraint`装饰器。我们在创建一个`IsUserAlreadyExist`验证装饰器演示 ```javascript import {registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator"; @ValidatorConstraint({ async: true }) class IsUserAlreadyExistConstraint implements ValidatorConstraintInterface { validate(userName: any, args: ValidationArguments) { return UserRepository.findOneByName(userName).then(user => { if (user) return false; return true; }); } } function IsUserAlreadyExist(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [], validator: IsUserAlreadyExistConstraint }); }; } class User { @IsUserAlreadyExist({ message: "User $value already exists. Choose another name." }) name: string; } ``` #### 同步验证 如果只是想简单的进行同步验证,可以使用`validateSync`代替`validate`。不过需要注意的是`validateSync`会忽略所有的异步验证。 ## class-transfomer中文文档 类转换器的作用是将普通的javascript对象转换成类对象。我们通过api端点或者json文件访问所得的是普通的json文本,一般我们通过`JSON.parse`把其转换成普通的javascript对象,但是有时候我们想让它变成一个类的对象而不是普通的javascript对象。比如用`class-validator`来验证从后端api获取的json字符串时,我们就需要自动把json转为待验证类的对象而不是一个js对象。 例如我们现在可以读取远程api的一个`users.json`的内容如下 ```json [{ "id": 1, "firstName": "Johny", "lastName": "Cage", "age": 27 }, { "id": 2, "firstName": "Ismoil", "lastName": "Somoni", "age": 50 }, { "id": 3, "firstName": "Luke", "lastName": "Dacascos", "age": 12 }] ``` 我们有一个`User`类 ```javascript export class User { id: number; firstName: string; lastName: string; age: number; getName() { return this.firstName + " " + this.lastName; } isAdult() { return this.age > 36 && this.age < 60; } } ``` 然后你想通过`user.json`来获取`User`的对象数组 ```javascript fetch("users.json").then((users: User[]) => { // you can use users here, and type hinting also will be available to you, // but users are not actually instances of User class // this means that you can't use methods of User class }); ``` 现在你可以获取`users[0].firstname`但是由于你获取的是普通的js对象而非`User`类的对象,所以你无法调用`users[0].getName()`方法,而**class-transformer**就是为了把普通的js对象按你的需求转换成类对象而生的。 你只要像下面这样就可以创建真正的`User[]`对象数组了 ```javascript fetch("users.json").then((users: Object[]) => { const realUsers = plainToClass(User, users); // now each user in realUsers is instance of User class }); ``` ### 安装 1. 安装class-transformer: `npm install class-transformer --save` 2. 安装`reflect-metadata` > reflect-metadata是必须的,具体使用请看[这篇博文](./2022-06-23-ts-decorator.md) 安装后在`app.ts`这种顶层文件你需要`import "reflect-metadata";` ### 基础方法 #### plainToClass 普通对象转换为类对象 ```javascript import {plainToClass} from "class-transformer"; let users = plainToClass(User, userJson); // to convert user plain object a single user. also supports arrays ``` #### plainToClassFromExist 普通对象合并已经创建的类实例 ```javascript const defaultUser = new User(); defaultUser.role = 'user'; let mixedUser = plainToClassFromExist(defaultUser, user); // mixed user should have the value role = user when no value is set otherwise. ``` #### classToPlain 类实例转换为普通对象 > 转换后可以使用`JSON.stringify`再转成普通的json文本 ```javascript import {classToPlain} from "class-transformer"; let photo = classToPlain(photo); ``` #### classToClass 克隆类实例 ```javascript import {classToClass} from "class-transformer"; let photo = classToClass(photo); ``` 可以使用`ignoreDecorators`选项去除所有原实例中的装饰器 #### serialize 直接把类实例转换为json文本,是不是数组都可以转换 ```javascript import {serialize} from "class-transformer"; let photo = serialize(photo); ``` #### deserialize 和 deserializeArray 直接把json文本转换为类对象 ```javascript import {deserialize} from "class-transformer"; let photo = deserialize(Photo, photo); ``` 如果json文本是个对象数组请使用`deserializeArray`方法 ```javascript import {deserializeArray} from "class-transformer"; let photos = deserializeArray(Photo, photos); ``` #### 强制类型安全 `plainToClass`会把所有的被转换对象的属性全部类实例的属性,即时类中并不存在某些属性 ```javascript import {plainToClass} from "class-transformer"; class User { id: number firstName: string lastName: string } const fromPlainUser = { unkownProp: 'hello there', firstName: 'Umed', lastName: 'Khudoiberdiev', } console.log(plainToClass(User, fromPlainUser)) // User { // unkownProp: 'hello there', // firstName: 'Umed', // lastName: 'Khudoiberdiev', // } ``` 你可以使用`excludeExtraneousValues`选项结合`Expose`装饰器来指定需要公开的属性 ```javascript import {Expose, plainToClass} from "class-transformer"; class User { @Expose() id: number; @Expose() firstName: string; @Expose() lastName: string; } const fromPlainUser = { unkownProp: 'hello there', firstName: 'Umed', lastName: 'Khudoiberdiev', } console.log(plainToClass(User, fromPlainUser, { excludeExtraneousValues: true })) // User { // id: undefined, // firstName: 'Umed', // lastName: 'Khudoiberdiev' // } ``` ### 子类型转换 #### 嵌套对象 由于现在Typescript对反射还没有非常好的支持,所以你需要使用`@Type`装饰器来隐式地指定属性所属的类 ```javascript import {Type, plainToClass} from "class-transformer"; export class Album { id: number; name: string; @Type(() => Photo) photos: Photo[]; } export class Photo { id: number; filename: string; } let album = plainToClass(Album, albumJson); // now album is Album object with Photo objects inside ``` #### 多类型选项 一个嵌套的子类型也可以匹配多个类型,这可以通过判断器实现。判断器需要指定一个` property`,而被转换js对象中的嵌套对象的也必须拥有与`property`相同的一个字段,并把值设置为需要转换的子类型的名称。判断器还需要指定所有的子类型值以及其名称,具体示例如下 ```javascript import {Type, plainToClass} from "class-transformer"; const albumJson = { "id": 1, "name": "foo", "topPhoto": { "id": 9, "filename": "cool_wale.jpg", "depth": 1245, "__type": "underwater" } } export abstract class Photo { id: number; filename: string; } export class Landscape extends Photo { panorama: boolean; } export class Portrait extends Photo { person: Person; } export class UnderWater extends Photo { depth: number; } export class Album { id: number; name: string; @Type(() => Photo, { discriminator: { property: "__type", subTypes: [ { value: Landscape, name: "landscape" }, { value: Portrait, name: "portrait" }, { value: UnderWater, name: "underwater" } ] } }) topPhoto: Landscape | Portrait | UnderWater; } let album = plainToClass(Album, albumJson); // now album is Album object with a UnderWater object without `__type` property. ``` 此外可以设置`keepDiscriminatorProperty: true`,这样可以把判断器的属性也包含在转换后的对象中 ### 排除与公开 #### 公开方法的返回值 添加`@Expose`装饰器即可公开getter和方法的返回值 ```javascript import {Expose} from "class-transformer"; export class User { id: number; firstName: string; lastName: string; password: string; @Expose() get name() { return this.firstName + " " + this.lastName; } @Expose() getFullName() { return this.firstName + " " + this.lastName; } } ``` #### 公开属性为不同名称 如果要使用其他名称公开某些属性,可以通过为`@Expose`装饰器指定`name`选项来实现: ```javascript import {Expose} from "class-transformer"; export class User { @Expose({ name: "uid" }) id: number; firstName: string; lastName: string; @Expose({ name: "secretKey" }) password: string; @Expose({ name: "fullName" }) getFullName() { return this.firstName + " " + this.lastName; } } ``` #### 跳过指定属性 有时您想在转换过程中跳过一些属性。这可以使用`@Exclude`装饰器完成: ```javascript import {Exclude} from "class-transformer"; export class User { id: number; email: string; @Exclude() password: string; } ``` 现在,当您转换用户时,`password`属性将被跳过,并且不包含在转换结果中。 #### 根据操作决定跳过 我们可以通过`toClassOnly`或者`toPlainOnly`来控制一个属性在哪些操作中需要排除 ```javascript import {Exclude} from "class-transformer"; export class User { id: number; email: string; @Exclude({ toPlainOnly: true }) password: string; } ``` 现在`password`属性将会在`classToPlain`操作中排除,相反的可以使用`toClassOnly` #### 跳过类的所有属性 你可以通过在类上添加`@Exclude`装饰器并且在需要公开的属性上添加`@Expose`装饰器来只公开指定的属性 ```javascript import {Exclude, Expose} from "class-transformer"; @Exclude() export class User { @Expose() id: number; @Expose() email: string; password: string; } ``` 另外,您可以在转换期间设置排除策略: ```javascript import {classToPlain} from "class-transformer"; let photo = classToPlain(photo, { strategy: "excludeAll" }); ``` 这时你不需要在添加`@Exclude`装饰器了 #### 跳过私有属性或某些前缀属性 我们可以排除公开具有指定前缀的属性以及私有属性 ```javascript import {Expose} from "class-transformer"; export class User { id: number; private _firstName: string; private _lastName: string; _password: string; setName(firstName: string, lastName: string) { this._firstName = firstName; this._lastName = lastName; } @Expose() get name() { return this.firstName + " " + this.lastName; } } const user = new User(); user.id = 1; user.setName("Johny", "Cage"); user._password = 123; const plainUser = classToPlain(user, { excludePrefixes: ["_"] }); // here plainUser will be equal to // { id: 1, name: "Johny Cage" } ``` #### 使用组来控制排除的属性 ```javascript import {Exclude, Expose} from "class-transformer"; @Exclude() export class User { id: number; name: string; @Expose({ groups: ["user", "admin"] }) // this means that this data will be exposed only to users and admins email: string; @Expose({ groups: ["user"] }) // this means that this data will be exposed only to users password: string; } let user1 = classToPlain(user, { groups: ["user"] }); // will contain id, name, email and password let user2 = classToPlain(user, { groups: ["admin"] }); // will contain id, name and email ``` #### 使用版本范围来控制公开和排除的属性 如果要构建具有不同版本的API,则class-transformer具有非常有用的工具。您可以控制应在哪个版本中公开或排除模型的哪些属性。示例 ```javascript import {Exclude, Expose} from "class-transformer"; @Exclude() export class User { id: number; name: string; @Expose({ since: 0.7, until: 1 }) // this means that this property will be exposed for version starting from 0.7 until 1 email: string; @Expose({ since: 2.1 }) // this means that this property will be exposed for version starting from 2.1 password: string; } let user1 = classToPlain(user, { version: 0.5 }); // will contain id and name let user2 = classToPlain(user, { version: 0.7 }); // will contain id, name and email let user3 = classToPlain(user, { version: 1 }); // will contain id and name let user4 = classToPlain(user, { version: 2 }); // will contain id and name let user5 = classToPlain(user, { version: 2.1 }); // will contain id, name nad password ``` ### 特殊处理 #### 将日期字符串转换为Date对象 有时,您的JavaScript对象中有一个以字符串格式接收的Date。您想从中创建一个真正的javascript Date对象。您只需将Date对象传递给`@Type`装饰器即可完成此操作: > 当从类对象反向转换为普通对象时registrationDate将会被转回为字符串 ```javascript import {Type} from "class-transformer"; export class User { id: number; email: string; password: string; @Type(() => Date) registrationDate: Date; } ``` 当您想将值转换为`Number`, `String`, `Boolean` 类型时也是这样做 #### 数组处理 当你想转换数组时,你必须使用`@Type`装饰器指定数组项的类型也可以使用自定义的数组类型 `Set`和`Map`也是一样 ```javascript import {Type} from "class-transformer"; export class AlbumCollection extends Array { // custom array functions ... } export class Photo { id: number; name: string; @Type(() => Album) albums: Album[]; // albums: AlbumCollection; 使用自定义类型 } export class Skill { name: string; } export class Weapon { name: string; range: number; } export class Player { name: string; @Type(() => Skill) skills: Set; @Type(() => Weapon) weapons: Map; } ``` ### 自定义转换 #### 基本使用 你可以使用`@Transform`添加额外的数据转换,例如当你想把通过普通对象中的字符串日期转换后的`date`对象继续转换变成`moment`库的对象: ```javascript import {Transform} from "class-transformer"; import * as moment from "moment"; import {Moment} from "moment"; export class Photo { id: number; @Type(() => Date) @Transform(value => moment(value), { toClassOnly: true }) date: Moment; } ``` 现在当执行`plainToClass`转换后的对象中的`date`属性将是一个`Moment`对象。`@Transform`同样支持组和版本。 #### 高级用法 `@Transform`有更多的参数给你创建自定义的转换逻辑 ```javascript @Transform((value, obj, type) => value) ``` | 参数 | 描述 | | :------ | ------------------------ | | `value` | 自定义转换执行前的属性值 | | `obj` | 转换源对象 | | `type` | 转换的类型 | #### 其他装饰器 | 签名 | 示例 | | ------------------------ | ---------------------------------------------------- | | `@TransformClassToPlain` | `@TransformClassToPlain({ groups: ["user"] })` | | `@TransformClassToClass` | `@TransformClassToClass({ groups: ["user"] })` | | `@TransformPlainToClas` | `@TransformPlainToClass(User, { groups: ["user"] })` | 上述装饰器接受一个可选参数:`ClassTransformOptions`-转换选项,例如groups, version, name,示例: ```javascript @Exclude() class User { id: number; @Expose() firstName: string; @Expose() lastName: string; @Expose({ groups: ['user.email'] }) email: string; password: string; } class UserController { @TransformClassToPlain({ groups: ['user.email'] }) getUser() { const user = new User(); user.firstName = "Snir"; user.lastName = "Segal"; user.password = "imnosuperman"; return user; } } const controller = new UserController(); const user = controller.getUser(); ``` `user`对象将包含firstname,latstname和email #### 使用泛型 由于目前Typescript对反射的支持还没有完善,所以只能使用其它替代方案,具体可以查看[这个例子](https://github.com/pleerock/class-transformer/tree/master/sample/sample4-generics) #### 隐式类型转换 > 你如果将class-validator与class-transformer一起使用,则可能不想启用此功能。 根据Typescript提供的类型信息,启用内置类型之间的自动转换。默认禁用。 ```javascript import { IsString } from 'class-validator' class MyPayload { @IsString() prop: string } const result1 = plainToClass(MyPayload, { prop: 1234 }, { enableImplicitConversion: true }); const result2 = plainToClass(MyPayload, { prop: 1234 }, { enableImplicitConversion: false }); /** * result1 will be `{ prop: "1234" }` - notice how the prop value has been converted to string. * result2 will be `{ prop: 1234 }` - default behaviour */ ``` #### 循环引用 如果`User`包含一个`Photo`类型的`photos`数组属性,而`Photo`又包含一个属性链接到`User`,则转换过程中此属性会被忽略,除了`classToClass`操作。