TypeScript 对象序列化:从 JSON 到 protobuf
序列化是将复杂的数据结构(对象、数组、类实例)转换为适合存储或网络传输格式的过程。反向过程——反序列化——则从该格式恢复对象。在作为 JavaScript 严格类型化超集的 TypeScript 中,序列化尤为重要:不仅需要传输数据,还要保留类型、方法和类结构。
在本文中,我们将探讨 TypeScript 中对象序列化的主要方法和工具,从内置功能到专门的库。
1. 原生序列化:JSON.stringify 和 JSON.parse
最简单且最常用的方法是使用内置方法 JSON.stringify() 和 JSON.parse()。然而,这种方法存在一些限制:
- 无法序列化函数、
undefined、Symbol、BigInt(在旧版本中)。 - 不保留对象的原型——反序列化后你将得到一个普通对象,而不是类实例。
- 循环引用会导致错误。
// 简单序列化示例interface User { id: number; name: string; email: string;}
const user: User = { id: 1, name: "伊万", email: "ivan@example.com"};
const jsonString = JSON.stringify(user);console.log(jsonString); // {"id":1,"name":"伊万","email":"ivan@example.com"}
const parsedUser: User = JSON.parse(jsonString);console.log(parsedUser.name); // 伊万对于简单数据,这已经足够,但如果你需要处理类,则需要更高级的解决方案。
2. 使用 toJSON 和 fromJSON 方法的自定义序列化
TypeScript(以及 JavaScript)允许在类中定义 toJSON() 方法,该方法会在通过 JSON.stringify 序列化时被调用。对于反序列化,可以创建一个静态方法 fromJSON。
class Person { constructor( public firstName: string, public lastName: string, public age: number ) {}
// 序列化方法 toJSON() { return { fullName: `${this.firstName} ${this.lastName}`, age: this.age }; }
// 反序列化静态方法 static fromJSON(json: any): Person { const [firstName, lastName] = json.fullName.split(' '); return new Person(firstName, lastName, json.age); }}
const person = new Person("安娜", "彼得罗娃", 30);const json = JSON.stringify(person);console.log(json); // {"fullName":"安娜 彼得罗娃","age":30}
const restoredPerson = Person.fromJSON(JSON.parse(json));console.log(restoredPerson.firstName); // 安娜这种方法提供了完全的控制权,但需要为每个类编写样板代码。
3. class-transformer 库
class-transformer 是 TypeScript 中最流行的序列化/反序列化库之一。它允许通过装饰器将普通对象转换为类实例,反之亦然。
安装:
npm install class-transformer reflect-metadata使用示例:
import { plainToClass, classToPlain, Expose, Transform } from "class-transformer";import "reflect-metadata";
class Product { @Expose() id: number;
@Expose() name: string;
@Expose() @Transform(({ value }) => value.toUpperCase()) // 自定义转换 category: string;
@Expose({ name: "price_with_tax" }) // 字段重命名 price: number;
constructor(id: number, name: string, category: string, price: number) { this.id = id; this.name = name; this.category = category; this.price = price; }
getInfo(): string { return `${this.name} (${th