Schema 3.5.0
Payload representation changes
Producers and consumers should move to the 3.5.0 generated schemas together, or use a reader that
accepts both — observer-js does.
Changed — payloads are records
ClientEvent.payload, ClientIssue.payload, ClientMetaData.payload and ExtensionStat.payload
changed from an optional string to an optional map of primitives —
Record<string, boolean | string | number> in the generated TypeScript (Avro:
["null", {"type": "map", "values": ["boolean", "string", "double"]}], keeping the null-first
optional convention).
Producers no longer JSON.stringify every payload before buffering it, and consumers read payload
fields directly instead of parsing a nested JSON document.
// before
{ "type": "MEDIA_TRACK_MUTED", "payload": "{\"trackId\":\"t-1\",\"kind\":\"audio\"}" }
// 3.5.0
{ "type": "MEDIA_TRACK_MUTED", "payload": { "trackId": "t-1", "kind": "audio" } }Why — measured, not argued
Measured on Node 22 with realistic samples carrying 35–40 payloads:
| Effect | |
|---|---|
| Payload bytes on the wire | ~12% smaller — a pre-stringified payload gets every quote escaped when the whole sample serialises |
| Serialising the sample | ~18% faster end to end (0.37 ms → 0.30 ms per sample) |
| The events portion alone | ~54% faster (0.042 ms → 0.020 ms) |
| Server-side decode | ~3% saved — one parse instead of 1 + N |
Each payload was previously walked twice: once by its own JSON.stringify at buffering time (30–40
calls per sample, now zero, along with their intermediate-string garbage) and once more
character-by-character for escaping. Payload fields also become directly queryable without a
second parse.
Codec impact
- Protobuf: the payload still travels as a JSON
string, because proto3 cannot express a union-valued map — the same conventionattachmentsuses. The protobuf codec converts it through its JSON-fields mechanism, keyed on the field name, so all four payload fields are covered and protobuf field numbering is unchanged. - JSON: no change needed. Events, issues, meta items and extension stats are value lists written whole, and object values were already handled generically.
The TypeScript generator learned to render union-valued Avro maps for this.
Upgrading
// before
const payload = JSON.parse(event.payload ?? '{}');
// 3.5.0
const payload = event.payload ?? {};A reader that must accept both generations does what the observer does: pass an object through untouched, parse a string.
3.7.0 widened this again
A payload value stayed a primitive in 3.5.0 and 3.6.0. 3.7.0 widened it to free-form JSON, so a payload may nest objects and arrays — again with no change to either wire format.
← Back to version history · 3.4.0 →