ADR-0025: JSON encoding is shape-aware, never value-sniffed
- Status: Accepted
- Date: 2026-06-25
- Deciders: jka
- Decision: D25
Context
Inside the core, values are Tcl dicts and strings (ADR-0011). Tcl cannot tell the
string "hi there" from the two-element list {hi there}, or the string "10"
from a number. A generic dict-to-JSON encoder must therefore guess types from
values, which mis-types strings that happen to look like lists or numbers.
Decision
The boundary encoder never infers types from values.
- The envelope is fixed:
idis a JSON string,okis a JSON boolean, and a reply carries eitherresultorerror({code, message}). resultand eventparamsdefault to flat objects whose values are JSON strings.- An operation that needs another shape (numbers, arrays, nested objects)
declares it:
rio::wirekeeps a result-encoder registry keyed by operation name, and dispatch passes the operation name with the reply. Unregistered operations use the flat encoder. - Inbound JSON is parsed with tcllib's
json::json2dict. rio::wire::strescapes every C0 control character, as RFC 8259 requires.
Consequences
- Encoding is total and predictable, and each non-flat result is explicit in
rio-core/wire.tcl. - A registered encoder lists its keys by name, so a key added to an operation's result is dropped on the wire unless the encoder is updated. Core tests that call the operation directly do not notice. Since ADR-0110, each encoder has a guard test that encodes the real operation's result and compares the key set.
- Events are flat objects. An event that would need nested data carries an identifier instead, and the client fetches the data (ADR-0106).