devalue
Rich type serialization — Date, Map, Set, BigInt, RegExp, undefined, and circular references survive the round-trip.
If your API returns JavaScript types that JSON.stringify can't handle, devalue is the answer. It's a text-based format (like JSON) that preserves rich types through a round-trip.
The problem with JSON
JSON.stringify silently breaks several JavaScript types:
.(new ('2026-01-01')) // "2026-01-01T00:00:00.000Z" (string, not Date)
.(new ([['a', 1]])) // "{}" (empty object)
.(new ([1, 2, 3])) // "{}" (empty object)
.(42n) // throws TypeError
.({ : }) // "{}" (key is dropped)If your procedure returns any of these types, the client receives broken data with plain JSON.
How devalue fixes it
devalue (by Rich Harris, the creator of Svelte) serializes values into a JSON string with embedded type markers. When decoded, the original types are reconstructed:
import { , } from 'silgi/devalue'
const = {
: new ([['alice', { : 1 }]]),
: new (['admin', 'user']),
: 9007199254740993n,
: new ('2026-01-01'),
: /test/gi,
: ,
}
const = () // JSON string with type info
const = ()
.users // Map { "alice" => { id: 1 } } -- not an empty object
.tags // Set { "admin", "user" } -- not an empty object
.count // 9007199254740993n -- BigInt preserved
.created // Date 2026-01-01T00:00:00.000Z -- Date object, not string
.pattern // /test/gi -- RegExp preserved
.optional // undefined -- not missingSupported types
| Type | JSON | devalue |
|---|---|---|
| String, Number, Boolean, null | Yes | Yes |
| Object, Array | Yes | Yes |
| Date | Becomes string | Preserved |
| Map | Becomes {} | Preserved |
| Set | Becomes {} | Preserved |
| BigInt | Throws | Preserved |
| RegExp | Becomes {} | Preserved |
| undefined | Dropped | Preserved |
| Circular references | Throws | Preserved |
Server integration
devalue works through content negotiation, just like MessagePack. The client sends an Accept header and the server responds in devalue format:
Accept: application/x-devalue+json --> response encoded with devalueRequest bodies with Content-Type: application/x-devalue+json are decoded automatically on the server.
Both serve() and handler() support this out of the box. No configuration needed.
When to use devalue vs MessagePack
| Scenario | Recommended format |
|---|---|
| Standard APIs (strings, numbers, arrays) | JSON (default) |
| Smaller payloads, mobile, bandwidth | MessagePack |
| Returning Date, Map, Set, BigInt | devalue |
| Circular references in data | devalue |
| Need binary + Date support | MessagePack |
If you only need Date support and smaller payloads, MessagePack is a better choice. If you need Map, Set, BigInt, or circular references, devalue is the way to go.
devalue is text-based (the output is a JSON string), so it doesn't save bandwidth like MessagePack. Its advantage is type fidelity, not size.
Direct API
If you need to use devalue outside of the client/server flow:
import { , } from 'silgi/devalue'
const = (myData) // returns a string
const = () // returns the original value with types intactWhat's next?
- MessagePack — binary protocol for smaller payloads
- WebSocket — persistent connections for real-time features
- Client — the full client setup guide