Serialization overview
Two scenarios require turning JS values into bytes:
- Cluster wire — a
tellto a remote actor needs the message serialized for TCP transmission. - Persistence — events / snapshots / durable state stored on disk or in a journal.
The framework’s Serializer interface is the abstraction.
Two built-ins ship:
| Serializer | Format | When |
|---|---|---|
JsonSerializer | JSON | Default. Human-debuggable, widely understood. |
CborSerializer | CBOR (RFC 8949) | Compact, binary-native, faster. |
Plus an extension point: implement Serializer<T> for
Protobuf, Avro, MessagePack, etc.
How it works
Section titled “How it works”Sender side: tell(remoteRef, msg) → SerializationExtension picks serializer for typeof(msg) → serializer.toBinary(msg) → Uint8Array → wrapped with serializerId + manifest + bytes → cluster transport sends over the wire
Receiver side: cluster transport receives bytes → SerializationExtension picks serializer by serializerId → serializer.fromBinary(bytes, manifest) → reconstituted value → delivered to actor's onReceiveThe serializerId + manifest tell the receiver which
serializer to use + what type to reconstruct.
The Serializer interface
Section titled “The Serializer interface”interface Serializer<T = unknown> { readonly id: number; // unique per serializer readonly name: string; // human-readable includesManifest: boolean; manifest(obj: T): string; // type tag toBinary(obj: T): Uint8Array; fromBinary(bytes: Uint8Array, manifest: string): T;}Small surface — encode, decode, identify. The framework’s
SerializationExtension maps classes to serializers (via
bind); on serialization, the extension looks up which
serializer to use for the value’s class.
Default behavior
Section titled “Default behavior”const system = ActorSystem.create('my-app');// → JsonSerializer registered as default// → Every value serializes as JSON unless a specific serializer is boundIf you do nothing, every value’s bytes are JSON. Plain objects, arrays, strings, numbers — all work. Class instances serialize as plain objects (losing methods).
Picking JSON or CBOR
Section titled “Picking JSON or CBOR”| Aspect | JSON | CBOR |
|---|---|---|
| Wire size | Larger | ~20-40 % smaller |
| Speed | Slower than CBOR for binary data | Faster |
| Debugging | Trivial (text) | Requires CBOR-aware tool |
| Binary fields | base64-encoded (wasteful) | Native binary |
| Library support | Universal | Solid in JS land |
For most apps, JSON is fine — the perf difference doesn’t matter and the debuggability is valuable.
For bandwidth-sensitive cases (large cluster, lots of events, IoT-style payloads), CBOR wins meaningfully.
See JsonSerializer + CborSerializer for details.
Custom serializers
Section titled “Custom serializers”For Protobuf / Avro / etc., implement Serializer<T> and
register:
import { SerializationExtensionId } from 'actor-ts';
const ext = system.extension(SerializationExtensionId);ext.bind(MyEvent, new ProtobufSerializer<MyEvent>(MyEventSchema));Now every MyEvent value uses Protobuf for serialization
across the wire / to journal. Other types still use JSON.
See Custom serializers for the full setup.
What can be serialized
Section titled “What can be serialized”The default JSON serializer handles:
- Plain objects (
{ a: 1, b: 'two' }). - Arrays.
- Strings, numbers, booleans,
null. - Nested combinations of the above.
Cannot serialize:
- Functions / closures (no method survival).
- Class instances with methods (methods lost; data fields survive).
Map/Set(JSON-stringified as{}).Date(round-trips through string; type info lost).BigInt(throws).- Symbols (silently dropped).
CBOR handles a few more (binary Uint8Array natively, Map
as a real map), but not functions.
For complex types, convert to plain objects before sending:
// ✗ref.tell({ when: new Date() });
// ✓ref.tell({ when: Date.now() });Where serialization fires
Section titled “Where serialization fires”1. cluster wire — every cross-node tell.2. PersistentActor.persist(event) — events to the journal.3. DurableStateActor.persist(state) — state to the store.4. Snapshot writes — snapshots to the snapshot store.5. DistributedData — replicated state across the cluster.In-process tells don’t serialize — the receiver gets the exact in-memory reference. Serialization happens only at process / disk / cluster boundaries.
Where to next
Section titled “Where to next”- JSON serializer — the default.
- CBOR serializer — the binary alternative.
- Custom serializers — Protobuf, Avro, etc.
- Messages — what shapes survive serialization.
- Refs across nodes — the cluster wire that consumes serializers.