Skip to content

JSON

Model mapping

JSON is the baseline. An object becomes a list of edges, in the order its keys appear. A key whose value is an array becomes a repeated label: the array is not a value in the model, it is the same label occurring more than once.

JSON Document
{"a":1,"b":2} [(a,1),(b,2)]
{"m":[A,B]} [(m,A),(m,B)]
{"m":[A]} [(m,A)] — one edge, indistinguishable from {"m":A}
{"m":[]} zero m edges — the label is simply absent from the result

No temporal types. JSON has none, so a reader MUST NOT produce date, time, or datetime on its own. A date-looking string stays a string unless a schema upgrades it in stage 2. A writer MUST stringify a temporal leaf to ISO-8601.

No NaN or Infinity. Those tokens are not valid JSON. Writing one MUST fail with write.unsupported-value (§8.3.8), unconditionally — not gated behind a strict mode, and never by substituting null. A substituted NaN and a genuine null produce the identical JSON token and read back as the identical Document value, so the substitution could never be detected or undone afterwards.

Bare nested arrays are rejected. [[1,2],[3,4]] has inner elements with no label and therefore no edge to occupy. A reader MUST reject it rather than flatten it.

An empty array is a legitimate zero-edge encoding, not an error. {"m":[]} reads to zero m edges: the label simply doesn't appear in the result, the same as if the key were absent entirely. This is JSON-specific and does not extend to OML's [...] array sugar — a bare m: [] in OML MUST be rejected (verified live: empty array is not allowed), because OML's array syntax is sugar for a run of same-label edges and a zero-length run is indistinguishable from no sugar being used at all. JSON has no such ambiguity: its arrays are a real container independent of the Document model's edge-repetition mechanism, so an empty one is just as meaningful as a non-empty one.

Top level. A JSON document may have many top-level keys, which becomes many top-level edges. That is legal, and it is also the shape XML cannot carry.

Duplicate keys: last one wins. {"a":1,"a":2} reads as [(a,2)] — one edge, not two. The JSON grammar itself is silent on duplicate names (RFC 8259 permits but discourages them and does not define a resolution), so this is an Omnist policy choice, not a JSON requirement — chosen because it is already the de facto behavior of essentially every mainstream JSON parser (a later key overwriting an earlier one when built into a map), so codifying it costs nothing across ports and avoids inventing divergent behavior where none currently exists in practice. This is unrelated to a repeated label becoming a repeated edge ({"m":[A,B]}[(m,A),(m,B)], see above) — that is JSON's own array syntax, not a duplicate-key situation. Duplicate keys collapse to one edge; a JSON array under a single key does not.

Interleaving is lost on write. Edges sharing a label are grouped into one key regardless of position, because a JSON object cannot express [(m,A),(x,X),(m,B)]. See §7.3.

Worked example

The schema:

record Address  { "street": string, "city": string }
record LineItem { "sku": string, "qty": integer, "price": number }

record Order {
    "id":           string,
    "status":       string,
    "total":        number,
    "address":      Address,
    "items" [1,]:   LineItem,
    "coupon" [0,1]: string,
}

record Root { "order": Order }
root Root

The same order in JSON:

{"order": {"id": "A1", "status": "shipped", "total": 29.97,
  "address": {"street": "1 Main", "city": "London"},
  "items": [{"sku": "W", "qty": 3, "price": 9.99},
            {"sku": "G", "qty": 1, "price": 9.99}]}}

reads to:

[ (order, [ (id,      "A1"),
            (status,  "shipped"),
            (total,   29.97),
            (address, [ (street, "1 Main"), (city, "London") ]),
            (items,   [ (sku, "W"), (qty, 3), (price, 9.99) ]),
            (items,   [ (sku, "G"), (qty, 1), (price, 9.99) ]) ]) ]

The two items objects arrive as two edges sharing the label items, which is exactly what "items" [1,]: LineItem counts. Writing back reverses it: two items edges regroup into one JSON array. Had there been one line item, the writer would emit a bare object rather than a one-element array, by the count-1 rule in §7.3.

total is 29.97 and reads as a number with no schema involved. qty reads as an integer. Neither needs materialization; the schema's role here is to check shape, not to convert. A JSON leaf that did need conversion — a datetime field carrying "2024-01-01T12:30:00" — would stay a string after stage 1 and only become a datetime in stage 2.

A real-world example. The running Order schema is clean by design — it has no unions and no open key sets, so it never has to confront JSON's actual worst case. package.json does: its author/bugs/repository fields can each be either a string or an object, and scripts/dependencies are genuinely open key sets. Modeling it is exactly the kind of case any (§3.7) exists for — see package.osd and its two fixtures in ../examples/.

Parity gaps

Chapter 9's status table (§9.3) is the authority on which implementations ship a JSON codec. This page states no per-port status of its own: a copy here can only go stale relative to the ledger.

There is no JSON-specific entry in §9.4 — see that section for whatever's currently open.