class-transformer

class-transformer

TypeScript对象转换与序列化工具库

class-transformer是一个功能丰富的TypeScript库,专门用于对象转换和序列化。它支持将普通JavaScript对象转换为类实例,也可以进行反向操作。该库提供了多种实用方法,如plainToInstance和instanceToPlain,可以处理嵌套对象、暴露getter和方法返回值,以及选择性地跳过属性。class-transformer在处理API数据和复杂对象结构时尤其有效,适用于前端和后端开发。

class-transformerTypeScript对象转换序列化反序列化Github开源项目

class-transformer

构建状态 代码覆盖率 npm版本

这是ES6和TypeScript的时代。如今,您比以往任何时候都更多地使用类和构造函数对象。 Class-transformer允许您将普通对象转换为某个类的实例,反之亦然。 它还允许基于特定条件序列化/反序列化对象。 这个工具在前端和后端都非常有用。

plunker中可以查看如何与Angular 2一起使用的示例。 源代码可在这里找到。

目录

什么是class-transformer

在JavaScript中有两种类型的对象:

  • 普通(字面量)对象
  • 类(构造函数)对象

普通对象是Object类的实例。 有时它们被称为字面量对象,通过{}符号创建。 类对象是具有自己定义的构造函数、属性和方法的类的实例。 通常通过class符号定义它们。

那么,问题是什么?

有时您想将普通JavaScript对象转换为您拥有的ES6。 例如,如果您正在从后端、某个API或JSON文件加载JSON, 在JSON.parse之后,您得到的是普通JavaScript对象,而不是您所拥有的类的实例。

例如,您有一个正在加载的users.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类:

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; } }

假设您正在从users.json文件下载User类型的用户,您可能想编写以下代码:

fetch('users.json').then((users: User[]) => { // 您可以在这里使用users,类型提示也会对您可用, // 但users实际上并不是User类的实例 // 这意味着您不能使用User类的方法 });

在这段代码中,您可以使用users[0].id,也可以使用users[0].firstNameusers[0].lastName。 但是您不能使用users[0].getName()users[0].isAdult(),因为"users"实际上是 普通JavaScript对象的数组,而不是User对象的实例。 当您说它是users: User[]时,您实际上欺骗了编译器。

那么该怎么办呢?如何让users数组成为User对象的实例而不是普通JavaScript对象? 解决方案是创建User对象的新实例,并手动将所有属性复制到新对象中。 但是一旦您有更复杂的对象层次结构,事情可能很快就会出错。

有替代方案吗?是的,您可以使用class-transformer。这个库的目的是帮助您将普通JavaScript 对象映射到您拥有的类的实例。

这个库对于在API中暴露的模型也很棒, 因为它提供了很好的工具来控制您的模型在API中暴露的内容。 以下是它看起来的样子:

fetch('users.json').then((users: Object[]) => { const realUsers = plainToInstance(User, users); // 现在realUsers中的每个用户都是User类的实例 });

现在您可以使用users[0].getName()users[0].isAdult()方法了。

安装

Node.js

  1. 安装模块:

    npm install class-transformer --save

  2. 需要reflect-metadata shim,也安装它:

    npm install reflect-metadata --save

    并确保在全局位置导入它,比如app.ts:

    import 'reflect-metadata';
  3. 使用了ES6特性,如果您使用的是旧版本的node.js,可能需要安装es6-shim:

    npm install es6-shim --save

    并在全局位置导入它,比如app.ts:

import 'es6-shim';

浏览器

  1. 安装模块:

    npm install class-transformer --save

  2. 需要安装 reflect-metadata shim:

    npm install reflect-metadata --save

    index.html 的 head 中添加 reflect-metadata 的 <script> 标签:

    <html> <head> <!-- ... --> <script src="node_modules/reflect-metadata/Reflect.js"></script> </head> <!-- ... --> </html>

    如果你使用的是 Angular 2,应该已经安装了这个 shim。

  3. 如果你使用 system.js,你可能想在 mappackage 配置中添加以下内容:

    { "map": { "class-transformer": "node_modules/class-transformer" }, "packages": { "class-transformer": { "main": "index.js", "defaultExtension": "js" } } }

方法

plainToInstance

此方法将普通的 JavaScript 对象转换为特定类的实例。

import { plainToInstance } from 'class-transformer'; let users = plainToInstance(User, userJson); // 将用户普通对象转换为单个用户实例。也支持数组

plainToClassFromExist

此方法将普通对象转换为实例,使用已填充的目标类实例对象。

const defaultUser = new User(); defaultUser.role = 'user'; let mixedUser = plainToClassFromExist(defaultUser, user); // 当没有设置值时,mixedUser 应具有 role = user 的值,否则使用原值。

instanceToPlain

此方法将类对象转换回普通的 JavaScript 对象,之后可以使用 JSON.stringify

import { instanceToPlain } from 'class-transformer'; let photo = instanceToPlain(photo);

instanceToInstance

此方法将类对象转换为该类对象的新实例。 这可以被视为对象的深度克隆。

import { instanceToInstance } from 'class-transformer'; let photo = instanceToInstance(photo);

你还可以在转换选项中使用 ignoreDecorators 选项来忽略类中使用的所有装饰器。

serialize

你可以使用 serialize 方法直接将模型序列化为 JSON:

import { serialize } from 'class-transformer'; let photo = serialize(photo);

serialize 适用于数组和非数组。

deserialize 和 deserializeArray

你可以使用 deserialize 方法从 JSON 反序列化模型:

import { deserialize } from 'class-transformer'; let photo = deserialize(Photo, photo);

要使反序列化适用于数组,请使用 deserializeArray 方法:

import { deserializeArray } from 'class-transformer'; let photos = deserializeArray(Photo, photos);

强制类型安全实例

plainToInstance 方法的默认行为是设置普通对象中的所有属性, 即使这些属性未在类中指定。

import { plainToInstance } from 'class-transformer'; class User { id: number; firstName: string; lastName: string; } const fromPlainUser = { unkownProp: 'hello there', firstName: 'Umed', lastName: 'Khudoiberdiev', }; console.log(plainToInstance(User, fromPlainUser)); // User { // unkownProp: 'hello there', // firstName: 'Umed', // lastName: 'Khudoiberdiev', // }

如果这种行为不适合你的需求,你可以在 plainToInstance 方法中使用 excludeExtraneousValues 选项, 同时需要将所有类属性标记为公开。

import { Expose, plainToInstance } 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(plainToInstance(User, fromPlainUser, { excludeExtraneousValues: true })); // User { // id: undefined, // firstName: 'Umed', // lastName: 'Khudoiberdiev' // }

处理嵌套对象

当你尝试转换具有嵌套对象的对象时, 需要知道你要转换的对象类型。 由于 TypeScript 目前还没有很好的反射能力, 我们应该明确指定每个属性包含的对象类型。 这是通过使用 @Type 装饰器完成的。

假设我们有一个包含照片的相册。 我们正尝试将相册普通对象转换为类对象:

import { Type, plainToInstance } from 'class-transformer'; export class Album { id: number; name: string; @Type(() => Photo) photos: Photo[]; } export class Photo { id: number; filename: string; } let album = plainToInstance(Album, albumJson); // 现在 album 是一个 Album 对象,其中包含 Photo 对象

提供多个类型选项

如果嵌套对象可以是不同类型,你可以提供一个额外的选项对象, 该对象指定一个鉴别器。鉴别器选项必须定义一个 property,用于保存对象的子类型名称, 以及嵌套对象可以转换为的可能 subTypes。子类型有一个 value,保存类型的构造函数, 还有一个 name,可以与鉴别器的 property 匹配。

假设我们有一个相册,它有一张顶级照片。但这张照片可以是某些不同类型。 我们正尝试将相册普通对象转换为类对象。普通对象输入必须定义 额外的属性 __type。默认情况下,此属性在转换过程中会被移除:

JSON 输入:

{ "id": 1, "name": "foo", "topPhoto": { "id": 9, "filename": "cool_wale.jpg", "depth": 1245, "__type": "underwater" } }
import { Type, plainToInstance } from 'class-transformer'; 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; }

让 album 等于使用 plainToInstance 将 albumJson 转换为 Album 实例。 // 现在 album 是一个 Album 对象,其中包含一个没有 __type 属性的 UnderWater 对象。


提示:这同样适用于具有不同子类型的数组。此外,你可以在选项中指定 `keepDiscriminatorProperty: true` 以在结果类中也保留判别器属性。

## 暴露 getter 和方法返回值

你可以通过在 getter 或方法上设置 `@Expose()` 装饰器来暴露它们的返回值:

```typescript
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 选项来实现:

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 装饰器来实现:

import { Exclude } from 'class-transformer'; export class User { id: number; email: string; @Exclude() password: string; }

现在当你转换 User 时,password 属性将被跳过,不会包含在转换结果中。

根据操作跳过

你可以控制在哪种操作中排除属性。使用 toClassOnlytoPlainOnly 选项:

import { Exclude } from 'class-transformer'; export class User { id: number; email: string; @Exclude({ toPlainOnly: true }) password: string; }

现在 password 属性只会在 instanceToPlain 操作中被排除。反之,使用 toClassOnly 选项。

跳过类的所有属性

你可以跳过类的所有属性,只暴露那些明确需要的属性:

import { Exclude, Expose } from 'class-transformer'; @Exclude() export class User { @Expose() id: number; @Expose() email: string; password: string; }

现在 idemail 将被暴露,而 password 在转换过程中将被排除。 或者,你可以在转换过程中设置排除策略:

import { instanceToPlain } from 'class-transformer'; let photo = instanceToPlain(photo, { strategy: 'excludeAll' });

在这种情况下,你不需要对整个类使用 @Exclude()

跳过私有属性或某些带前缀的属性

如果你的私有属性带有前缀,比如 _,那么你也可以在转换时排除这些属性:

import { instanceToPlain } from 'class-transformer'; let photo = instanceToPlain(photo, { excludePrefixes: ['_'] });

这将跳过所有以 _ 为前缀的属性。 你可以传入任意数量的前缀,所有以这些前缀开头的属性都将被忽略。 例如:

import { Expose, instanceToPlain } 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 = instanceToPlain(user, { excludePrefixes: ['_'] }); // 这里 plainUser 将等于 // { id: 1, name: "Johny Cage" }

使用分组控制排除的属性

你可以使用分组来控制哪些数据将被暴露,哪些不会:

import { Exclude, Expose, instanceToPlain } from 'class-transformer'; export class User { id: number; name: string; @Expose({ groups: ['user', 'admin'] }) // 这意味着这些数据只会暴露给用户和管理员 email: string; @Expose({ groups: ['user'] }) // 这意味着这些数据只会暴露给用户 password: string; } let user1 = instanceToPlain(user, { groups: ['user'] }); // 将包含 id、name、email 和 password let user2 = instanceToPlain(user, { groups: ['admin'] }); // 将包含 id、name 和 email

使用版本控制来控制暴露和排除的属性

如果你正在构建一个有不同版本的 API,class-transformer 为此提供了非常有用的工具。 你可以控制在哪个版本中应该暴露或排除模型的哪些属性。例如:

import { Exclude, Expose, instanceToPlain } from 'class-transformer'; export class User { id: number; name: string; @Expose({ since: 0.7, until: 1 }) // 这意味着这个属性将从版本 0.7 开始暴露,直到版本 1 email: string; @Expose({ since: 2.1 }) // 这意味着这个属性将从版本 2.1 开始暴露 password: string; } let user1 = instanceToPlain(user, { version: 0.5 }); // 将包含 id 和 name let user2 = instanceToPlain(user, { version: 0.7 }); // 将包含 id、name 和 email let user3 = instanceToPlain(user, { version: 1 }); // 将包含 id 和 name let user4 = instanceToPlain(user, { version: 2 }); // 将包含 id 和 name let user5 = instanceToPlain(user, { version: 2.1 }); // 将包含 id、name 和 password

将日期字符串转换为 Date 对象

有时你的普通 JavaScript 对象中会以字符串格式接收到一个日期。 而你想从中创建一个真正的 JavaScript Date 对象。 你可以简单地通过将 Date 对象传递给 @Type 装饰器来实现:

import { Type } from 'class-transformer'; export class User { id: number; email: string; password: string; @Type(() => Date) registrationDate: Date; }

当你想将值转换为这些类型时,同样的技术也可以用于 NumberStringBoolean 等基本类型。

使用数组

当使用数组时,你必须提供数组包含的对象类型。 这个类型需要在 @Type() 装饰器中指定:

import { Type } from 'class-transformer'; export class Photo { id: number; name: string; @Type(() => Album) albums: Album[]; }

你也可以使用自定义数组类型:

import { Type } from 'class-transformer'; export class AlbumCollection extends Array<Album> { // 自定义数组函数 ... } export class Photo { id: number; name: string; @Type(() => Album) albums: AlbumCollection; }

库会自动处理适当的转换。

ES6 集合 SetMap 也需要 @Type 装饰器:

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 对象:

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; }

现在当你调用 plainToInstance 并传入 Photo 对象的普通表示时, 它会将你的 photo 对象中的日期值转换为 moment 日期。 @Transform 装饰器还支持分组和版本控制。

高级用法

@Transform 装饰器提供了更多参数,让你可以配置如何进行转换。

@Transform(({ value, key, obj, type }) => value)
参数描述
value转换前的属性值
key被转换属性的名称
obj转换源对象
type转换类型
options传递给转换方法的选项对象

其他装饰器

签名示例描述
@TransformClassToPlain@TransformClassToPlain({ groups: ["user"] })使用 instanceToPlain 转换方法返回值,并在类上暴露属性
@TransformClassToClass@TransformClassToClass({ groups: ["user"] })使用 instanceToInstance 转换方法返回值,并在类上暴露属性
@TransformPlainToClass@TransformPlainToClass(User, { groups: ["user"] })使用 plainToInstance 转换方法返回值,并在类上暴露属性

上述装饰器接受一个可选参数: ClassTransformOptions - 转换选项,如分组、版本、名称

示例:

@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、lastName 和 email 属性,因为它们是暴露的变量。 email 属性也被暴露,因为我们提到了 "user.email" 分组。

处理泛型

由于 TypeScript 目前还没有很好的反射能力,泛型暂不支持。 一旦 TypeScript 团队为我们提供更好的运行时类型反射工具,泛型将会被实现。 不过,你可以使用一些技巧,也许能解决你的问题。 查看这个示例。

隐式类型转换

注意 如果你同时使用 class-validator 和 class-transformer,你可能不想启用此功能。

根据 Typescript 提供的类型信息,启用内置类型之间的自动转换。默认禁用。

import { IsString } from 'class-validator'; class MyPayload { @IsString() prop: string; } const result1 = plainToInstance(MyPayload, { prop: 1234 }, { enableImplicitConversion: true }); const result2 = plainToInstance(MyPayload, { prop: 1234 }, { enableImplicitConversion: false }); /** * result1 将是 `{ prop: "1234" }` - 注意 prop 值已被转换为字符串。 * result2 将是 `{ prop: 1234 }` - 默认行为 */

如何处理循环引用?

循环引用会被忽略。 例如,如果你正在转换包含 photos 属性(类型为 Photo)的 User 类, 而 Photo 包含指向其父 Useruser 链接,那么在转换过程中 user 将被忽略。 循环引用只在 instanceToInstance 操作中不被忽略。

Angular2 示例

假设你想下载用户并希望它们自动映射到 User 类的实例。

import { plainToInstance } from 'class-transformer'; this.http .get('users.json') .map(res => res.json()) .map(res => plainToInstance(User, res as Object[])) .subscribe(users => { // 现在 "users" 的类型是 User[],每个用户都可以使用 getName() 和 isAdult() 方法 console.log(users); });

你也可以在 providers 中注入 ClassTransformer 类作为服务,并使用其方法。

plunker 中查看如何与 Angular 2 一起使用的示例。 源代码在这里

示例

查看 ./sample 中的示例,了解更多使用示例。

发布说明

有关重大变更和发布说明的信息,请参阅此处

编辑推荐精选

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

Trae

Trae

字节跳动发布的AI编程神器IDE

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

热门AI辅助写作AI工具讯飞绘文内容运营AI创作个性化文章多平台分发AI助手
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

openai-agents-python

openai-agents-python

OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。

openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。

Hunyuan3D-2

Hunyuan3D-2

高分辨率纹理 3D 资产生成

Hunyuan3D-2 是腾讯开发的用于 3D 资产生成的强大工具,支持从文本描述、单张图片或多视角图片生成 3D 模型,具备快速形状生成能力,可生成带纹理的高质量 3D 模型,适用于多个领域,为 3D 创作提供了高效解决方案。

3FS

3FS

一个具备存储、管理和客户端操作等多种功能的分布式文件系统相关项目。

3FS 是一个功能强大的分布式文件系统项目,涵盖了存储引擎、元数据管理、客户端工具等多个模块。它支持多种文件操作,如创建文件和目录、设置布局等,同时具备高效的事件循环、节点选择和协程池管理等特性。适用于需要大规模数据存储和管理的场景,能够提高系统的性能和可靠性,是分布式存储领域的优质解决方案。

下拉加载更多