monorepo/apps/web2/mdx/content.mdx

1373 lines
34 KiB
Plaintext
Raw Normal View History

2024-09-28 14:58:45 +08:00
> 随着时间的推移,可能部分内容无法与官方最新版本同步,请自行对比查看,我有空会更新
用于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`操作。