RPC Serializer
RPC Serializers handle the serialization and deserialization of data sent between the client and server. They allow you to support complex data types beyond plain JSON, such as Date, BigInt, Set, and even custom classes.
Supported Data Types
RPCSerializer supports the following types by default:
| Type | Handler key | Notes |
|---|---|---|
| string | ||
| number | ||
| NaN | nan |
|
| boolean | ||
| null | ||
| undefined | undefined |
Ignore undefined properties |
| Date | date |
Includes Invalid Date. |
| BigInt | bigint |
|
| RegExp | regexp |
|
| URL | url |
|
| Record (object) | toJSON methods are ignored |
|
| Array | ||
| Set | set |
|
| Map | map |
|
| Blob | Unsupported in AsyncIteratorObject |
|
| File | Unsupported in AsyncIteratorObject |
|
| AsyncIteratorObject | Only at the root level | |
ReadableStream<Uint8Array> |
Only at the root level |
Custom Serializers
Add custom handlers with unique keys to support additional types, or reuse a built-in key to override the default behavior.
import { class RPCSerializerSerializes and deserializes data for the RPC protocol,
preserving native types like Date, BigInt, Set, and Map that plain JSON cannot represent.RPCSerializer } from '@orpc/client'
const const serializer: RPCSerializerserializer = new new RPCSerializer(options?: RPCSerializerOptions): RPCSerializerSerializes and deserializes data for the RPC protocol,
preserving native types like Date, BigInt, Set, and Map that plain JSON cannot represent.RPCSerializer({
RPCJsonSerializerOptions.handlers?: Record<string, RPCJsonSerializerHandler | undefined> | undefinedExtend or override the built-in type handlers used during serialization and deserialization.
Each key is a unique type identifier (e.g. `"date"`, `"bigint"`) and maps to a handler
that defines how to detect, serialize, and deserialize values of that type.
**Extending:** Add new keys to support custom types:
```ts
handlers: {
buffer: {
condition: (v) => v instanceof Buffer,
serialize: (v: Buffer) => v.toString('base64'),
deserialize: (s: string) => Buffer.from(s, 'base64'),
isTerminal: true,
}
}
```
**Overriding:** Use an existing key to replace a built-in handler:
```ts
handlers: {
date: {
condition: (v) => v instanceof Date,
serialize: (v: Date) => v.getTime(),
deserialize: (n: number) => new Date(n),
isTerminal: true,
}
}
```
**Disabling:** Set a key to `undefined` to remove a built-in handler:
```ts
handlers: { regexp: undefined }
```
Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `url`, `regexp`, `set`, `map`.handlers: {
person: {
condition: (v: unknown) => v is Person;
serialize: (v: Person) => {
name: string;
age: number;
};
deserialize: (v: any) => Person;
}
person: { // <- add support for Person
RPCJsonSerializerHandler.condition(value: unknown): booleancondition: v: unknownv => v: unknownv instanceof class PersonPerson,
RPCJsonSerializerHandler.serialize(value: any): unknownserialize: (v: Personv: class PersonPerson) => ({ name: stringname: v: Personv.Person.name: stringname, age: numberage: v: Personv.Person.age: numberage }),
RPCJsonSerializerHandler.deserialize(serialized: any): unknowndeserialize: v: anyv => new constructor Person(name: string, age: number): PersonPerson(v: anyv.name, v: anyv.age),
},
date: {
condition: (v: unknown) => v is Date;
serialize: (v: Date) => number;
deserialize: (v: any) => Date;
}
date: { // <- replace the default Date handler
RPCJsonSerializerHandler.condition(value: unknown): booleancondition: v: unknownv => v: unknownv instanceof var Date: DateConstructorEnables basic storage and retrieval of dates and times.Date,
RPCJsonSerializerHandler.serialize(value: any): unknownserialize: (v: Datev: Date) => v: Datev.Date.getTime(): numberReturns the stored time value in milliseconds since midnight, January 1, 1970 UTC.getTime(),
RPCJsonSerializerHandler.deserialize(serialized: any): unknowndeserialize: v: anyv => new var Date: DateConstructor
new (value: number | string | Date) => Date (+4 overloads)
Date(v: anyv),
},
},
})
Serialization Format
In most cases, serialized data includes two optional fields: json and meta. json contains JSON-serializable data. meta contains the metadata needed to deserialize values.
{
"json": {
"name": "John",
"age": 30,
"createdAt": "2024-01-01T00:00:00.000Z"
},
"meta": [
["date", "createdAt"]
]
}
With Files
If the data includes Blob or File, the serializer returns a FormData object. The data field contains a JSON string with json, meta, and maps, and the remaining fields contain the file parts.
const form = new FormData()
form.set('data', JSON.stringify({
json: {
name: 'Earth',
thumbnail: {},
images: [{}],
createdAt: '2022-01-01T00:00:00.000Z'
},
meta: [['date', 'createdAt']],
maps: [['thumbnail'], ['images', 0]]
}))
form.set('0', new Blob([''], { type: 'image/png' }))
form.set('1', new Blob([''], { type: 'image/png' }))
Direct File
If the entire data is a single Blob or File, it can be sent as-is without wrapping in FormData.
HTTP/1.1 200 OK
Content-Type: image/png
Content-Disposition: attachment; filename="earth.png"
Content-Length: 12345
Standard-Server: file
<binary data>
AsyncIteratorObject
When the output is an AsyncIteratorObject, it is sent as a Server-Sent Events stream. Each event contains one serialized chunk of data.
HTTP/1.1 200 OK
Content-Type: text/event-stream
event: message
data: {"json":{"name":"John","createdAt":"2024-01-01T00:00:00.000Z"},"meta":[["date","createdAt"]]}
event: message
data: {"json":{"name":"Jane","createdAt":"2024-01-02T00:00:00.000Z"},"meta":[["date","createdAt"]]}
ReadableStream<Uint8Array>
A ReadableStream<Uint8Array> is passed through as-is and streamed as binary data.
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Standard-Server: octet-stream
<binary chunk 1>
<binary chunk 2>
Learn More
The serializer is a small, self-contained module, making it easy to understand. To explore its behavior in detail, see the source code.