Zum Inhalt springen
Deutsch

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 }
  • 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.

  • Plain-Objekte, Arrays.
  • Strings, Zahlen, Booleans, null.
  • Verschachtelte Kombinationen.
  • Date, Uint8Array, Map, Set und bigint — über Type-Tags (__date__, __bytes__, __map__, __set__, __bigint__) zurück in echte Instanzen round-getrippt.
  • BidirectionalMap und BidirectionalMultiMap — 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 bei JSON.stringify.
  • toJSON() — wird wie bei JSON.stringify respektiert, sodass Luxon/Temporal-Wrapper und eigene toJSON-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:

TypVerhalten
undefinedWirft 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 / WeakSetWerfen einen SerializationError — prinzipiell nicht serialisierbar.
Zirkuläre ReferenzenWerfen einen SerializationError mit dem Key-Pfad (statt den Stack zu sprengen).
KlasseninstanzentoJSON() wird genutzt, wenn vorhanden; sonst als plain { ...fields } dekodiert — Methoden + Klassenidentität verloren.
Date / Map / Set / bigint / Uint8ArrayRound-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.

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.

Mit Nicht-JSON-Systemen interoperieren? → Custom (Protobuf/Avro)
Bandbreitenkritisch (große oder viele kleine Nachrichten)? → CBOR
Typisierte Schemas in Versionskontrolle nötig? → Custom (Protobuf)
Sonst → Bei JSON bleiben

Die meisten Produktions-Apps bleiben für Actor-Wire und Persistenz bei JSON — die Perf-/Größenvorteile eines Wechsels rechtfertigen selten die operative Komplexität.