>  随着时间的推移，可能部分内容无法与官方最新版本同步，请自行对比查看，我有空会更新

用于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<string>;
}
```

#### 验证Map

```javascript
import {MinLength, MaxLength} from "class-validator";

export class Post {

    @MaxLength(20, {
        each: true
    })
    tags: Map<string, string>;
}
```

#### 嵌套对象

一个验证的类中的某些属性可能是类一个的对象，比如`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<number>;

}
```

`@ValidatePromise()`也可以和`@ValidateNested()`一起使用

```javascript
import {ValidateNested, ValidatePromise} from "class-validator";

export class Post {

    @ValidateNested()
    @ValidatePromise()
    user: Promise<User>;

}
```

### 高级主题

#### 子类验证

如果定义一个从另一个继承的子类时，子类将自动继承父级的装饰器。如果在后代类中重新定义了属性，则装饰器将从该类和基类中继承

```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<boolean>
    }

    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<boolean> 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<Album> {
    // 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<Skill>;

    @Type(() => Weapon)
    weapons: Map<string, Weapon>;
}
```

### 自定义转换

#### 基本使用

你可以使用`@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`操作。