尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

class-transformer 完全指南:装饰器驱动的对象序列化、反序列化与类实例转换

class-transformer 完全指南:装饰器驱动的对象序列化、反序列化与类实例转换 序列化后端前端【免费下载链接】class-transformerDecorator-based transformation, serialization, and deserialization between objects and classes.项目地址https://gitcode.com/gh_mirrors/cl/class-transformer点击查看免费下载class-transformer 是一个基于 ES6/TypeScript 装饰器Decorator的转换库用于在普通对象plain/literal object与类实例class/constructor object之间互相转换并提供序列化、反序列化以及基于分组groups、版本version等条件控制字段暴露的能力。本文将以仓库 README.md 为骨架结合 src 源码与 sample 示例完整讲解其安装、核心 API、全部装饰器、配置选项与底层实现原理读完即可在你的前后端项目中直接落地。问题背景为什么需要 class-transformer在 JavaScript 中存在两类对象普通对象plain/literal objectObject类的实例通常通过{}字面量创建类对象class/constructor object通过class语法定义的、带有自有构造函数、属性和方法的实例。实际开发中从后端 API、JSON 文件或JSON.parse得到的数据都是普通对象而不是你定义的类实例。假设你有一个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; } }如果你写fetch(users.json).then((users: User[]) {...})虽然编译期有类型提示但运行时users只是普通对象数组users[0].getName()会直接报错——你“欺骗”了编译器。手动new User()再逐个拷贝属性在小对象上可行但一旦对象层级复杂就会失控。class-transformer 的目标正是帮你把普通对象可靠地映射为类实例。示例fetch(users.json).then((users: Object[]) { const realUsers plainToInstance(User, users); // now each user in realUsers is an instance of User class });此后users[0].getName()、users[0].isAdult()均可正常调用。同时该库对 API 层暴露的模型也很有用它提供了丰富的工具控制模型在接口中暴露哪些字段。安装与初始化Node.js 环境安装模块本体npm install class-transformer --save安装必需的reflect-metadatashimnpm install reflect-metadata --save并在全局入口如app.ts显式导入import reflect-metadata;reflect-metadata是装饰器读取design:type等元数据的依赖缺少它Type等功能无法工作仓库 package.json 的 devDependencies 中也固定依赖reflect-metadata0.1.13。库使用了 ES6 特性若运行在较旧版本的 Node.js 上建议再安装并导入 es6-shimnpm install es6-shim --saveimport es6-shim;浏览器环境同样安装模块与reflect-metadatanpm install class-transformer --save npm install reflect-metadata --save在index.html的head中引入 Reflect.js 脚本html head !-- ... -- script srcnode_modules/reflect-metadata/Reflect.js/script /head !-- ... -- /html如果使用 Angular 2通常已经内置该 shim。若使用 System.js 模块加载器在配置中补充map与packages{ map: { class-transformer: node_modules/class-transformer }, packages: { class-transformer: { main: index.js, defaultExtension: js } } }仓库当前版本为0.5.1见 package.json提供了 cjs/esm5/esm2015/umd 多套构建产物main指向./cjs/index.jsmodule指向./esm5/index.js。核心方法详解所有顶层方法都定义在 src/index.ts 中内部实际委托给ClassTransformer类src/ClassTransformer.ts的单例实例并统一通过TransformOperationExecutor执行转换。plainToInstance普通对象 → 类实例将普通对象转换为指定类的实例同时支持数组import { plainToInstance } from class-transformer; let users plainToInstance(User, userJson); // 单个对象或数组均可从源码看plainToInstance 内部调用classTransformer.plainToInstance后者在 ClassTransformer.ts 中以TransformationType.PLAIN_TO_CLASS创建执行器并调用executor.transform(undefined, plain, cls, ...)。plainToClassFromExist基于已有实例填充在已经存在且填充了部分数据的实例对象上应用转换——未在 plain 对象中提供的属性将保留原有实例中的值const defaultUser new User(); defaultUser.role user; let mixedUser plainToClassFromExist(defaultUser, user); // 未提供的字段保留 role user注意在 src/index.ts 中该方法被标注为deprecated注释指出当前实现存在“修改源对象”的缺陷建议谨慎使用。instanceToPlain类实例 → 普通对象把类对象转回普通字面量对象便于后续JSON.stringifyimport { instanceToPlain } from class-transformer; let photo instanceToPlain(photo);从源码看instanceToPlain 与旧名classToPlain均指向classTransformer.instanceToPlain后者以TransformationType.CLASS_TO_PLAIN执行。instanceToInstance类实例 → 类实例深拷贝把类对象转换成一个新的类实例可视为对象的深拷贝import { instanceToInstance } from class-transformer; let photo instanceToInstance(photo);转换时若传入ignoreDecorators: true选项则会忽略类上所有装饰器的影响详见下文“配置选项”。serialize直接序列化为 JSON 字符串import { serialize } from class-transformer; let photo serialize(photo);serialize同时支持数组与非数组。源码 serialize 本质是JSON.stringify(this.instanceToPlain(object, options))因此它会先按instanceToPlain规则包括各组/版本/排除策略处理后再序列化。该函数同样被标记为 deprecated官方建议改用JSON.stringify(instanceToPlain(object, options))。deserialize 与 deserializeArray从 JSON 字符串还原import { deserialize } from class-transformer; let photo deserialize(Photo, photo);数组场景使用deserializeArrayimport { deserializeArray } from class-transformer; let photos deserializeArray(Photo, photos);源码实现ClassTransformer.ts为JSON.parse(json)后委托给plainToInstance。这两个方法同样被标记 deprecated官方建议使用instanceToClass(cls, JSON.parse(json), options)或JSON.parse(json).map(value instanceToClass(cls, value, options))。强制类型安全的实例excludeExtraneousValuesplainToInstance的默认行为是把普通对象中的所有属性都拷贝进实例包括类中没有声明的多余属性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, // }若希望丢弃类声明之外的“外来属性”可在转换时传入excludeExtraneousValues: true。前提是类的每个属性都必须打上Expose()或Exclude装饰器否则该选项无法生效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 // }这一行为在接口文档 ClassTransformOptions 中有明确说明该选项要求目标类的每个属性都至少挂载一个来自本库的Expose或Exclude装饰器。嵌套对象转换Type 装饰器与判别器当转换的对象包含嵌套对象时必须显式告诉库每个属性的具体类型。由于 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); // now album is Album object with Photo objects insideType装饰器src/decorators/type.decorator.ts会同时读取design:type反射元数据并将typeFunction与选项注册进全局元数据存储defaultMetadataStorage。提供多种类型选项discriminator 判别器当嵌套对象可能是多种子类型之一时可以为Type传入包含discriminator的选项对象。判别器必须定义property承载子类型名称的字段名subTypes可转换的子类型列表每项包含value子类型的构造函数与name与判别器property的值匹配的字符串。例如一个专辑的封面照片可能是风景、肖像或水下照片三种类型JSON 输入{ id: 1, name: foo, topPhoto: { id: 9, filename: cool_wale.jpg, depth: 1245, __type: underwater } }TypeScript 定义与转换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; } let album plainToInstance(Album, albumJson); // now album is Album object with a UnderWater object without __type property.要点输入对象必须额外携带判别属性本例为__type: underwater该判别属性在转换过程中默认会被移除同样适用于包含多种子类型的数组若希望在结果类实例中保留判别属性可在选项中指定keepDiscriminatorProperty: true。相关接口见 type-options.interface.tsdiscriminator与keepDiscriminatorProperty默认false与 type-discriminator-descriptor.interface.ts。暴露 getter 与方法返回值Expose默认情况下只有属性会被转换。若希望 getter 或方法的返回值也参与转换为其添加Expose()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 选项通过Expose({ name: xxx })可以改变属性在转换结果中的键名常用于对外字段重命名如数据库字段映射到 API 字段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; } }接口定义见 expose-options.interface.tsname、since、until、groups、toClassOnly、toPlainOnly。装饰器实现src/decorators/expose.decorator.ts会将元数据注册进全局存储同时可作为类装饰器或属性装饰器使用。跳过特定属性Exclude使用Exclude装饰器可在转换时跳过指定属性import { Exclude } from class-transformer; export class User { id: number; email: string; Exclude() password: string; }此后无论plainToInstance还是instanceToPlainpassword都不会出现在结果中。按操作方向跳过toPlainOnly 与 toClassOnlyExclude支持指定仅在某一方向生效import { Exclude } from class-transformer; export class User { id: number; email: string; Exclude({ toPlainOnly: true }) password: string; }以上配置下password仅在instanceToPlain类 → 普通对象时被排除plainToInstance时仍会保留反之使用toClassOnly: true则仅在普通对象 → 类实例时排除。跳过类的全部属性类级 Exclude 与 excludeAll 策略可以在类上使用Exclude()再显式Expose()需要的字段实现“默认全排除、白名单放行”import { Exclude, Expose } from class-transformer; Exclude() export class User { Expose() id: number; Expose() email: string; password: string; }此时id、email被暴露password被排除。也可以在转换时通过strategy: excludeAll指定排除策略无需给类加Exclude()import { instanceToPlain } from class-transformer; let photo instanceToPlain(photo, { strategy: excludeAll });strategy的取值与默认行为见 class-transformer-options.interface.ts默认exposeAll全部暴露可选excludeAll。跳过私有/带前缀属性excludePrefixes若私有属性使用统一前缀如_可以按前缀批量排除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 }源码注释指出该选项仅对exposeAll策略生效见 class-transformer-options.interface.ts。用 groups 控制暴露/排除的属性groups用于按场景控制字段的暴露例如区分普通用户与管理员可见的数据import { Exclude, Expose, instanceToPlain } from class-transformer; export class User { id: number; name: string; Expose({ groups: [user, admin] }) // 对 users 和 admins 可见 email: string; Expose({ groups: [user] }) // 仅对 users 可见 password: string; } let user1 instanceToPlain(user, { groups: [user] }); // 包含 id、name、email、password let user2 instanceToPlain(user, { groups: [admin] }); // 包含 id、name、emailgroups是ClassTransformOptions的标准选项类型为string[]同样可作用于Exclude、Transform等装饰器。用版本号控制暴露/排除since 与 until构建多版本 API 时可用since/until控制属性在哪个版本区间暴露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、passwordversion选项类型为number语义为“since ≤ version until 才暴露”。日期字符串转 Date 对象当普通对象中的日期是字符串时可通过Type(() Date)在转换时生成真正的Date实例import { Type } from class-transformer; export class User { id: number; email: string; password: string; Type(() Date) registrationDate: Date; }同样的技巧也适用于Number、String、Boolean等原始类型实现值到目标类型的转换。数组与集合类型使用数组时必须通过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 ArrayAlbum { // custom array functions ... } export class Photo { id: number; name: string; Type(() Album) albums: AlbumCollection; }库会自动完成合适的转换。ES6 集合Set与Map同样需要Type指明内部元素类型export class Skill { name: string; } export class Weapon { name: string; range: number; } export class Player { name: string; Type(() Skill) skills: SetSkill; Type(() Weapon) weapons: Mapstring, Weapon; }附加数据转换Transform基本用法Transform允许自定义转换逻辑。例如把 plain → class 过程中的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时date值会被转换为 moment 对象。Transform同样支持groups与版本相关选项。仓库 sample5-custom-transformer 给出了组合用法Type(() Date)配合两个Transform分别指定toPlainOnly时转字符串、toClassOnly时转 moment。高级用法回调参数Transform的回调可接收完整上下文Transform(({ value, key, obj, type }) value)各参数含义ArgumentDescriptionvalue转换前的属性值。key被转换属性的名称。obj转换的源对象。type转换类型。options传入转换方法的选项对象。其中type对应 transformation-type.enum.ts 中的枚举PLAIN_TO_CLASS、CLASS_TO_PLAIN、CLASS_TO_CLASS可用于区分转换方向。Transform装饰器实现见 src/decorators/transform.decorator.ts。其他装饰器方法级转换还有一组针对方法返回值的装饰器可在调用方法时自动完成转换并暴露属性SignatureExampleDescriptionTransformClassToPlainTransformClassToPlain({ groups: [user] })以instanceToPlain转换方法返回值并将属性暴露到类上。TransformClassToClassTransformClassToClass({ groups: [user] })以instanceToInstance转换方法返回值并将属性暴露到类上。TransformPlainToClassTransformPlainToClass(User, { groups: [user] })以plainToInstance转换方法返回值并将属性暴露到类上。以上装饰器接受一个可选参数ClassTransformOptions如groups、version、name。示例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 提供更好的运行时类型反射工具后泛型才会被实现。目前可以通过一些变通手段解决可参考仓库中的泛型示例 sample4-generics内含SimpleCollection、SuperCollection等实现。隐式类型转换enableImplicitConversion该选项基于 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 } —— 默认行为 */注意如果你同时使用 class-validator 与 class-transformer通常不要开启此功能官方 README 有明确提示因为隐式转换可能与校验逻辑冲突。循环引用处理循环引用circular reference默认会被忽略。例如类User含photos: Photo[]属性而Photo又通过user属性指回父级User转换时这个回指会被忽略。唯一的例外是instanceToInstance操作此时循环引用不会被忽略。另外ClassTransformOptions中提供enableCircularCheck: true选项默认false见 default-options.constant.ts当你确信类型可能存在循环依赖时可显式开启循环检测。Angular 2 集成示例在 Angular 2 应用中可在 HTTP 响应流中直接映射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[]每个 user 都有 getName() 和 isAdult() 方法 console.log(users); });也可以把ClassTransformer类作为 service 注入providers直接调用其方法。更多示例与更新记录更多使用示例参见 sample 目录sample1-simple-usage基础用法、sample2-iheritance继承场景、sample3-custom-arrays自定义数组、sample4-generics泛型变通、sample5-custom-transformer自定义转换组合。破坏性变更与版本更新记录见 CHANGELOG.md。端到端行为验证可参考 test/functional 下的测试用例例如 transformation-option.spec.ts、custom-transform.spec.ts、circular-reference-problem.spec.ts 等。完整的转换选项速查所有转换方法均接受可选的ClassTransformOptions详见 class-transformer-options.interface.ts完整清单如下选项类型默认值说明strategyexcludeAll \| exposeAllexposeAll排除策略默认全部暴露。excludeExtraneousValuesbooleanfalse普通对象 → 类实例时排除类中未声明的外来属性需类属性有装饰器。groupsstring[]未定义仅转换带指定分组的属性。versionnumber未定义仅转换since ≤ version until区间内的属性。excludePrefixesstring[]未定义排除指定前缀开头的属性仅exposeAll策略生效。ignoreDecoratorsbooleanfalse忽略所有Expose/Exclude装饰器的影响其他选项仍需装饰器配合。targetMapsTargetMap[]未定义在不使用Type的情况下指定转换目标类型适合外部类或已有元数据。enableCircularCheckbooleanfalse开启循环引用检测。enableImplicitConversionbooleanfalse基于类型信息在内置类型间自动转换。exposeDefaultValuesbooleanfalse普通对象 → 类实例时为未提供的可选字段填充类中的默认值。exposeUnsetFieldsbooleantrue类 → 普通对象时值为undefined的字段默认会被省略设为false可保留。这些默认值集中定义在 default-options.constant.ts每次转换执行时通过{ ...defaultOptions, ...options }合并传入见 ClassTransformer.ts 各方法。掌握这张表即可灵活控制转换方向、字段暴露、类型转换与版本/分组策略覆盖前后端数据交互中的绝大多数场景。赞分享序列化后端前端【免费下载链接】class-transformerDecorator-based transformation, serialization, and deserialization between objects and classes.项目地址https://gitcode.com/gh_mirrors/cl/class-transformer点击查看免费下载相关推荐class-transformer 入门指南基于装饰器的 TypeScript 对象序列化与反序列化class transformer 入门指南基于装饰器的 TypeScript 对象序列化与反序列化 class transformer 是一个零外部依赖z序列化后端前端终极指南如何用class-transformer轻松实现对象序列化与反序列化终极指南如何用class transformer轻松实现对象序列化与反序列化 在现代JavaScript和TypeScript开发中我们经常需要处理对象序列序列化后端前端Hutool序列化对象序列化与反序列化Hutool序列化对象序列化与反序列化 引言为什么需要序列化 在现代分布式系统和微服务架构中对象序列化Serialization与反序列化Dese后端开发工具上一篇Windows远程桌面多用户破解RDPWrap.ini配置文件的完整使用指南下一篇Micronetes与Docker集成容器化微服务的本地开发最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表