Using Decorators to Serialize Entities in TypeScript

September, 30th 2024 2 min read

Serialization turns objects into formats like JSON so they can be stored, sent over the network, or logged safely. TypeScript decorators make this process much cleaner by allowing you to annotate which properties should or should not be serialized.
In this howto, you’ll build a fully working serialization system using decorators — step by step and with clean, modern TypeScript patterns.


Step 1: Enable Decorators in TypeScript

Decorators are still experimental, so you must explicitly enable them.

json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Install TypeScript if needed:

bash
npm install typescript --save-dev

Step 2: What Decorators Do

Decorators let you attach metadata or behavior to:

  • classes
  • properties
  • methods
  • parameters

In serialization, they help you mark which properties should be included.


Step 3: Creating a Serializable Property Decorator

Here’s a minimal decorator that records which properties should be serialized:

ts
function Serializable(target: any, key: string) {
  if (!target.constructor.serializableProperties) {
    target.constructor.serializableProperties = [];
  }
  target.constructor.serializableProperties.push(key);
}

This attaches an array serializableProperties to the class constructor.


Step 4: Applying the Decorator to an Entity

ts
class User {
  @Serializable
  firstName: string;

  @Serializable
  lastName: string;

  password: string; // sensitive, excluded

  constructor(firstName: string, lastName: string, password: string) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.password = password;
  }
}

Only decorated properties will be serialized.


Step 5: Implementing the Serialization Logic

ts
function serialize(instance: any): string {
  const output: any = {};
  const props = instance.constructor.serializableProperties || [];

  for (const key of props) {
    output[key] = instance[key];
  }

  return JSON.stringify(output);
}

Step 6: Serializing an Entity

ts
const user = new User('John', 'Doe', 'secretPassword');
console.log(serialize(user));
// {"firstName":"John","lastName":"Doe"}

The password field is ignored — exactly what we want.


Step 7: Adding More Power — Aliases and Metadata

A more advanced decorator allows renaming fields in output:

ts
function Serializable(alias?: string) {
  return (target: any, key: string) => {
    if (!target.constructor.serializableProperties) {
      target.constructor.serializableProperties = [];
    }

    target.constructor.serializableProperties.push({
      key,
      alias: alias || key,
    });
  };
}

Usage:

ts
class User {
  @Serializable('first_name')
  firstName: string;

  @Serializable('last_name')
  lastName: string;

  password: string;
}

Updated serialization:

ts
const u = new User('John', 'Doe', 'secretPassword');
console.log(serialize(u));
// {"first_name":"John","last_name":"Doe"}

Conclusion

Decorators are a clean, expressive way to control how TypeScript entities are serialized.
With this pattern you can:

  • mark safe properties
  • hide sensitive data
  • rename fields for APIs
  • extend serialization rules
  • support nested objects with custom logic

This makes your data structures more predictable, secure, and maintainable—especially in large-scale apps where consistency matters.

If you’d like, I can expand this into:

  • a Deep Serialization System (nested + typed)
  • a version for backend frameworks (NestJS, Express, tRPC)
  • a more advanced .mdx demo with real-world entity patterns