JSON-Serializer
JsonSerializer ist der Default-Serializer. Er umschließt
JSON.stringify + JSON.parse (Bytes sind UTF-8) mit einem
Baum-Walk, der zusätzlich Date, Uint8Array, Map, Set und
bigint über Type-Tags round-trippt.
import { JsonSerializer } from 'actor-ts';
const serializer = new JsonSerializer();const bytes = serializer.toBinary({ kind: 'increment', n: 1 });// → Uint8Array von '{"kind":"increment","n":1}' als UTF-8
const decoded = serializer.fromBinary(bytes, '');// → { kind: 'increment', n: 1 }Warum JSON der Default ist
Abschnitt betitelt „Warum JSON der Default ist“- Universell — jede Sprache hat einen JSON-Parser.
- Menschenlesbar — öffne eine Journal-Datei mit
cat; sieh die Events. - Schema-flexibel — kein Compile-Schritt; Typen entwickeln sich durch ganz normale Code-Änderungen weiter (+ Schema- Migrations-Adapter).
- Kampferprobt — jede Web-API spricht JSON.
Für die meisten Apps wirst du nie Serializer wechseln — JSON deckt es ab.
Was er behandelt
Abschnitt betitelt „Was er behandelt“- Plain-Objekte, Arrays.
- Strings, Zahlen, Booleans,
null. - Verschachtelte Kombinationen.
Date,Uint8Array,Map,Setundbigint— über Type-Tags (__date__,__bytes__,__map__,__set__,__bigint__) zurück in echte Instanzen round-getrippt.BidirectionalMapundBidirectionalMultiMap— die beiden Framework-Klassen mit eigenen Tags (__bidirectionalmap__, #1035;__bidirectionalmultimap__, #1037), damit sie ohne Adapter im persistenten State liegen können. Geschrieben wird nur die Vorwärtsrichtung — Paare bei der ersten, eine Adjazenzliste bei der zweiten; die Umkehrung wird beim Dekodieren rekonstruiert.NaN/Infinity/-Infinity/-0,RegExp(Source + Flags),URL,Error(Name + Message + Cause — ohne Stack) und jedes Typed Array /DataView/ArrayBuffer— ebenfalls über Type-Tags round-getrippt (#889).Number/String/Boolean-Wrapper-Objekte entpacken zu ihrem Primitiv, wie beiJSON.stringify.toJSON()— wird wie beiJSON.stringifyrespektiert, sodass Luxon/Temporal-Wrapper und eigenetoJSON-Typen als ihre JSON-Form encodieren.- Nutzerdaten, die wie ein Tag aussehen — ein Objekt, dessen
einziger Key ein reservierter Tag-Name ist — werden beim Encode
in ein
__literal__-Escape gewrappt und round-trippen als Plain-Data. Ein Tag wird nur interpretiert, wenn er der einzige eigene Key eines Objekts ist.
Die Persistenz nutzt exakt dieses Tree-Format: Jeder Journal, Snapshot-Store und Durable-State-Store schreibt seine Payloads durch denselben getaggten Tree (siehe was Events und State enthalten dürfen).
Worauf du achten musst:
| Typ | Verhalten |
|---|---|
undefined | Wirft einen SerializationError (strenger als rohes JSON.stringify, das es verwirft). Der Persistenz-Payload-Codec verwirft stattdessen undefined-Objekt-Properties wie JSON.stringify und erhält undefined in Wertpositionen (Array-Slots, Set-Member, Map-Einträge) über einen Tag (#889). CborSerializer trägt es überall nativ, Keys eingeschlossen — die eine Stelle, an der sich die beiden Codecs unterscheiden (#1036). |
Function / Symbol / Promise / WeakMap / WeakSet | Werfen einen SerializationError — prinzipiell nicht serialisierbar. |
| Zirkuläre Referenzen | Werfen einen SerializationError mit dem Key-Pfad (statt den Stack zu sprengen). |
| Klasseninstanzen | toJSON() wird genutzt, wenn vorhanden; sonst als plain { ...fields } dekodiert — Methoden + Klassenidentität verloren. |
Date / Map / Set / bigint / Uint8Array | Round-trippen als echte Instanzen, aber das JSON trägt eine actor-ts-spezifische Tag-Form (z. B. {"__date__":"…"}) — ein einfacher (Nicht-actor-ts-)JSON-Leser sieht keinen blanken Wert. |
Für die meisten Actor-Nachrichten ist das okay — die Message-Konventionen des Frameworks raten ohnehin von nicht serialisierbaren Formen ab.
JSON ist größer als Binärformate für dieselben Daten:
- Zahlen sind Dezimal-Strings (
"42"sind 4 Bytes vs. 2 bei CBOR). - Feldnamen wiederholen sich (jeder Record hat
"kind":"..."). - Binärdaten (
Uint8Array) werden base64-kodiert (33 % Overhead).
Für textlastige oder sich wiederholende Daten ist JSONs Overhead klein. Für binärlastige Daten (Bilder, kodierte Payloads) spart CBOR spürbar.
Performance
Abschnitt betitelt „Performance“Grobe Zahlen pro Serialize-/Deserialize-Paar:
- Kleines Objekt (~50 Bytes): ~1–2 Mikrosekunden.
- Mittleres Objekt (~1 KB): ~5–15 Mikrosekunden.
- Großes Objekt (~100 KB): ~1–2 ms.
JSON.parse / JSON.stringify sind nativer Code in V8/JSC —
schnell genug, dass JSON-Serialisierung selten zum Bottleneck wird.
Wann auf etwas anderes wechseln
Abschnitt betitelt „Wann auf etwas anderes wechseln“Mit Nicht-JSON-Systemen interoperieren? → Custom (Protobuf/Avro)Bandbreitenkritisch (große oder viele kleine Nachrichten)? → CBORTypisierte Schemas in Versionskontrolle nötig? → Custom (Protobuf)Sonst → Bei JSON bleibenDie meisten Produktions-Apps bleiben für Actor-Wire und Persistenz bei JSON — die Perf-/Größenvorteile eines Wechsels rechtfertigen selten die operative Komplexität.
Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- Serialization-Übersicht — das große Bild.
- CBOR-Serializer — die binäre Alternative.
- Eigene Serializer — Protobuf / Avro / usw.
- Nachrichten — die Konventionen für serialisierungsfreundliche Formen.
