monorepo/apps/web2/mdx/content.mdx

1373 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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