Object Serialization in TypeScript: A Complete Guide
Serialization is the process of converting an object into a byte stream or string for transmission over a network or storage. In TypeScript, this task is complicated by the need to preserve types, support polymorphism, and work with classes. Let's look at the main tools and approaches.
1. Built-in Serialization: JSON.stringify / JSON.parse
The basic mechanism in JavaScript/TypeScript is the global JSON object. It only serializes simple types: strings, numbers, booleans, arrays, and flat objects. Methods, symbols, undefined, Date, and class instances are lost.
// Example: class serializationclass User { constructor( public name: string, private password: string, public createdAt: Date = new Date() ) {}}
const user = new User('Alice', 'secret123');const json = JSON.stringify(user);// Result: {"name":"Alice","createdAt":"2025-03-24T10:00:00.000Z"}// password is not included in JSON (private), but Date becomes a string// During deserialization, we get a plain object, not a User instance
const parsed: User = JSON.parse(json) as User;console.log(parsed instanceof User); // falseProblems:
- Type loss (a class instance becomes an
object). - Private fields, methods, and getters are not serialized.
- Dates, Map, Set, BigInt require manual conversion.
- Polymorphism is not supported — you need to manually restore the type.
2. The class-transformer Library
class-transformer is the most popular solution for serializing/deserializing classes in TypeScript. It uses decorators to describe transformation rules.
Installation
npm install class-transformer reflect-metadataUsage Example
import { plainToClass, classToPlain, Expose, Transform, Type } from 'class-transformer';import 'reflect-metadata';
class Address { @Expose() city!: string;
@Expose() street!: string;}
class Employee { @Expose() id!: number;
@Expose() name!: string;
@Expose() @Type(() => Address) // nested transformation address!: Address;
@Expose() @Transform(({ value }) => new Date(value)) // custom transformation hireDate!: Date;
// Method is not serialized get displayName(): string { return `${this.name} (ID: ${this.id})`; }}
// Deserialization from plain objectconst plainData = { id: 1, name: 'Bob', address: { city: 'Moscow', street: 'Tverskaya' }, hireDate: '2024-01-15T00:00:00.000Z'};
const employee = plainToClass(Employee, plainData);console.log(employee instanceof Employee); // trueconsole.log(employee.hireDate instanceof Date); // true
// Serialization backconst plain = classToPlain(employee);console.log(plain); // { id: 1, name: 'Bob', address: { city: 'Moscow', street: 'Tverskaya' }, hireDate: '2024-01-15T00:00:00.000Z' }Advantages:
- Automatic type restoration (nested objects, arrays).
- Polymorphism support via
@Typewith a discriminator. - Value transformation (dates, numbers, custom formats).
- Works with private fields via
@Expose.
Disadvantages:
- Dependency on decorators (requires
experimentalDecoratorsin tsconfig). - Not suitable for high-load systems (reflection slows down performance).
- Bundle size ~15KB (gzip).
3. Protocol Buffers with protobuf-ts
For microservice architecture and gRPC, Protocol Buffers are often used. The TypeScript implementation protobuf-ts generates typed