The canonical data form

Status: implemented

toData(s) turns a schema — functions and all — into plain data: no functions, normalized toward one spelling per set, and comparable.

equal(toData(or(true, false)))(toData(boolean))   // true
subset(toData({ a: 42 }))(toData({ a: number }))  // true
subset(toData({ a: number }))(toData({ a: 42 }))  // false

A schema is a set of values, and the data form represents the set, not the spelling. A union is merged kind by kind, a constant inside its whole kind is absorbed (or(42, number) is all numbers), a member another covers is dropped, and what is left is ordered canonically. equal compares two such forms, subset asks whether every value of one is a value of the other, and cmp orders schemas for sorting.

The normalization is deliberately not complete, so equal is sound but not exact. When it says true, the two schemas admit the same values. When it says false, they may still admit the same values but be spelled differently. Two examples are [or(number, string)] against or([number], [string]), and two recursive rules that differ only in name. The data form's canonical form and subset sections list the known cases.

subset is sound and deliberately incomplete: when it says true, it is true; it may say false for a pair whose inclusion it cannot establish.

The other outputs are written from the data form: the TypeScript text and JSON Schema both go through toData, and the data form has a reader of its own, validate. A recursive schema becomes a named rule, which is how those outputs write it once.

Examples

The demo on this page runs the Subset lesson: { a: 42 } and { a: number }, which both accept {"a":42}. The comparison under the graph reports that the first is a subset of the second, and not equal to it. Type {"a":7} to see only the second accept it. The lesson is defined in sample.f.mjs, and the module page's demo lists it among all the others. The Canonical form section of every demo shows a schema's data form.

Reference

  • JSON Schema has no inclusion test; TypeScript's assignability is the closest counterpart of subset.

Where it is implemented

toData, equal, cmp, subset and validate in data, whose README describes the representation.

Demo

Every value of { a: 42 } is a value of { a: number }, and not the other way round: the comparison under the graph says so. Type {"a":7} to see only the second accept it.

{}a42

TypeScript:

{readonly"a":42}

JSON Schema:

{"type":"object","properties":{"a":{"const":42}},"required":["a"],"additionalProperties":{"not":{}}}
Canonical form (toData)
export default [{},{"object":[{"props":{"a":{"number":[42]}},"rest":{}}]}];

Included: every value this schema accepts, that one accepts too.

equal → false
subset(this)(that) → true
subset(that)(this) → false

parse · ok

export default {"a":42};

validate · ok

export default {"a":42};

Emergent Testing

Idle. Press Run to start the suite.

  • ./proof.f.mjs