Skip to content

OpenAPI Serializer

OpenAPI Serializers handle one-way serialization to JSON-friendly formats. They let you partially support complex data types beyond plain JSON, such as Date, BigInt, Set, and even custom classes.

Supported Data Types

OpenAPISerializer supports the following types by default:

TypeHandler keySerializedNotes
string
number
NaNnannull
boolean
null
undefinedundefinednullIgnore undefined properties
DatedateISO String, null
BigIntbigintstring
RegExpregexpstring
URLurlstring
Record (object)toJSON methods are ignored
Array
Setsetarray
Mapmaparray
BlobUnsupported in Event Iterator
FileUnsupported in Event Iterator
Event Iterator (AsyncIteratorObject)Only at the root level
ReadableStream<Uint8Array>Only at the root level

WARNING

To better support Blob, File, and ReadableStream<Uint8Array> at the root level in cross-origin scenarios, extend your CORS allowlist to allow clients to send and receive the Content-Disposition and Standard-Server headers. Learn more in the Standard Server documentation. If you use the CORS Plugin, include them in allowHeaders and exposeHeaders:

ts
const cors = new CORSHandlerPlugin({
  allowHeaders: ['Content-Disposition', 'Standard-Server'],
  exposeHeaders: ['Content-Disposition', 'Standard-Server'],
})

Limitations

OpenAPI Serializers are designed for one-way serialization to JSON-friendly formats. For example, a Date is serialized to an ISO string and remains a string after deserialization unless you add custom logic or plugins.

In complex cases like mixed files with other data or nested structures in query strings, OpenAPI Serializer might use bracket notation to represent nested data, which has its own limitations. See Bracket Notation Limitations for details.

TIP

If you use OpenAPI Link to connect your client and server, follow Expanding Type Support for OpenAPI Link to restore native types on the client.

Custom Serializers

Add custom handlers with unique keys to support additional types, or reuse a built-in key to override the default behavior.

ts
import { 
OpenAPISerializer
} from '@orpc/openapi'
const
serializer
= new
OpenAPISerializer
({
handlers
: {
person
: { // <- add support for Person
condition
:
v
=>
v
instanceof
Person
,
serialize
: (
v
:
Person
) => ({
name
:
v
.
name
,
age
:
v
.
age
}),
},
date
: { // <- replace the default Date handler
condition
:
v
=>
v
instanceof
Date
,
serialize
: (
v
: Date) =>
v
.
getTime
(),
}, }, })

Use a custom serializer with OpenAPIHandler and OpenAPILink

ts
const handler = new OpenAPIHandler(router, {
  serializer,
})

const link = new OpenAPILink(contract, {
  serializer,
})

Serialization Format

In most cases, serialized data is JSON-serializable.

json
{
  "name": "John",
  "age": 30,
  "createdAt": "2024-01-01T00:00:00.000Z"
}

With Files

If the data includes nested Blob or File, the serializer returns a FormData object using Bracket Notation. Non-file values are converted to strings, and null or undefined fields are omitted.

ts
const form = new FormData()

form.append('name', 'Earth')
form.append('thumbnail', new Blob([''], { type: 'image/png' }))
form.append('images[0]', new Blob([''], { type: 'image/png' }))
form.append('createdAt', '2022-01-01T00:00:00.000Z')

INFO

images[0] means the first item in images array.

Direct File

If the entire data is a single Blob or File, it can be sent as-is without wrapping in FormData.

http
HTTP/1.1 200 OK
Content-Type: image/png
Content-Disposition: attachment; filename="earth.png"
Content-Length: 12345
Standard-Server: file

<binary data>

INFO

If the receiver mistakenly handles this payload as a regular (non-file) body, set the standard-server header to help the receiver detect the actual data type and handle it correctly. Learn more about this header in the Standard Server Documentation.

Event Iterator (AsyncIteratorObject)

When the output is an event iterator (AsyncIteratorObject), it is sent as a Server-Sent Events stream. Each event contains one serialized chunk of data.

http
HTTP/1.1 200 OK
Content-Type: text/event-stream

event: message
data: {"name":"John","createdAt":"2024-01-01T00:00:00.000Z"}

event: message
data: {"name":"Jane","createdAt":"2024-01-02T00:00:00.000Z"}

ReadableStream<Uint8Array>

A ReadableStream<Uint8Array> is passed through as-is and streamed as binary data.

http
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Standard-Server: octet-stream

<binary chunk 1>
<binary chunk 2>

INFO

If the receiver mistakenly handles this payload as a regular (non-stream) body, set the standard-server header to help the receiver detect the actual data type and handle it correctly. Learn more about this header in the Standard Server Documentation.

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.

Released under the MIT License.