aontu

See the canonical form

Print what a document means (defaults, disjunctions and all) instead of what it resolves to.

Rendered from docs/how-to/see-canonical-form.md in the engine repository, where a correction belongs, and where the test suite executes every example on this page.

Generation answers “what does this resolve to?”, and in answering it erases the thing a schema author cares about: what the document still admits. The canonical form keeps that. Write timeout.aon:

timeout: *30|integer

Now evaluate it both ways:

$ aontu timeout.aon
{
  "timeout": 30
}
$ aontu --canon timeout.aon
{"timeout":*30|integer}

Generation resolves the default; --canon keeps the whole default-and-type so you can see what a caller may still override. Where information has fully arrived, the two agree: a constraint met by a concrete value canons to just the value. Write pinned.aon:

a: 1
a: number
$ aontu --canon pinned.aon
{"a":1}

From code, the same view is the canon property of a unified value:

aontu.unify('a: *1 | number').canon   // '{"a":*1|number}'
aontu.unify('a: 1 a: number').canon   // '{"a":1}'
v, _ := a.Unify("a: *1 | number")   // v.Canon() == `{"a":*1|number}`

A conflict throws before there is anything to read, so ask for the wreckage with collect: true (collect errors); the failed path reads nil:

aontu.unify('a: number a: string', { collect: true }).canon  // '{"a":nil}'

Canon is deterministic and reparseable, which is why it carries more weight than a debugging view: it is the text the aon1- pin is derived from, so “same canon” is this project’s working definition of “same meaning” (pin a document hash).

The exhaustive rendering rules (key order, number forms, what recursion renders as) are in the language reference under Canonical form.