---
url: https://node-ts.github.io/bus/guide/serializers.md
description: >-
  How messages are written as JSON, and how generated message types restore
  their Dates, Maps, Sets, bigints and classes.
---

# Serializers

A serializer writes messages to a string when they're sent, and reads them back when they're received. Workflow state goes through it too. This page covers the default serializer, the message types it uses to restore your types, and writing your own serializer.

## The problem with JSON

Messages are sent as JSON, which has no Dates or classes. Read back naively, a message is plain data:

```ts
const charged = new CreditCardCharged('tok_visa', 1200, new Date())
const received = JSON.parse(JSON.stringify(charged))

// Without message types, `chargedAt` is a string and `received` isn't a CreditCardCharged
console.log(typeof received.chargedAt) // 'string'
```

## The default serializer and message types

The default `JsonSerializer` restores Dates, Maps, Sets, bigints and class instances at any depth of a message or workflow state, using the message types generated by `bus generate-message-types` from `@node-ts/bus-cli`. The generator reads your TypeScript source, so messages stay plain classes, with no decorators or `reflect-metadata`.

```sh
npm i -D @node-ts/bus-cli typescript
npx bus generate-message-types --entry 'src/messages/**/*.ts'
```

Pass the generated `messageTypes` to every bus that receives the messages:

```ts
const bus = Bus.configure()
  // Restores the Dates, Maps, Sets, bigints and classes in received messages
  .withMessageTypes(messageTypes)
  .build()
```

A bus that handles messages from several message libraries passes the message types of each, as in `withMessageTypes(orderMessageTypes, messageTypes)`. They're merged when the bus is built, and `build()` throws `MessageTypesConflict` if two of them map the same `$name` to different types. At `initialize()`, a bus that receives messages throws `MessageTypesMissing`, naming what's missing, unless it has an entry for every message it handles and every workflow state it persists. Send-only buses don't need message types.

Messages stay plain JSON on the wire, so services that don't use the generated file, or aren't written in TypeScript, can still read them. Dates are sent as ISO strings, Maps as objects, Sets as arrays and bigints as strings.

The generator's options, the types it supports and its known limits are in the [bus-cli README](https://github.com/node-ts/bus/tree/master/packages/bus-cli#bus-generate-message-types).

::: warning Constructors aren't run
Received messages and workflow state are created from their class' prototype, and their fields copied onto it. Constructor logic and field initializers don't run, so a field that's missing from the payload stays `undefined`. `#private` fields aren't sent or restored.
:::

## Custom serializers

A serializer implements `Serializer` from `@node-ts/bus-core`. The bus passes its message types to `deserialize` and `toClass`, so one serializer can be shared by several buses. Register it with `withSerializer()`:

```ts
/**
 * Writes messages as JSON, like the default serializer. A starting point for
 * a serializer that, say, encrypts some fields.
 */
export class CustomSerializer implements Serializer {
  private readonly json = new JsonSerializer()

  serialize<T extends object>(obj: T): string {
    return this.json.serialize(obj)
  }

  deserialize<T extends object>(
    val: string,
    classType: ClassConstructor<T>,
    // The message types of the bus that's reading the message
    messageTypes?: MessageTypes
  ): T {
    return this.json.deserialize(val, classType, messageTypes)
  }

  toPlain<T extends object>(obj: T): object {
    return this.json.toPlain(obj)
  }

  toClass<T extends object>(
    obj: object,
    classConstructor: ClassConstructor<T>,
    messageTypes?: MessageTypes
  ): T {
    return this.json.toClass(obj, classConstructor, messageTypes)
  }
}

Bus.configure()
  .withMessageTypes(messageTypes)
  .withSerializer(new CustomSerializer())
```

## See also

* [Class serializer](/guide/serializers/class-serializer), which generated message types replaced in 2.0
* [`Serializer`](/api/bus-core/interfaces/Serializer) and [`generateMessageTypes`](/api/bus-cli/functions/generateMessageTypes) in the API reference
