parse and validate

Status: implemented

const person = open({ name: string, age: number })
const alice = { name: 'Alice', age: 30, admin: true }
parse(person)(alice)     // ['ok', { name: 'Alice', age: 30 }]
validate(person)(alice)  // ['ok', alice] — the same object, `admin` included

Both readers walk a schema against a value, both accept exactly the same values, and both report the same error on failure. They differ in what a success carries:

parsevalidate
result on successa freshly constructed valuethe value it was given
undeclared member (where admitted)accepted, absent from the resultaccepted, still there
absent optional memberstill absentstill absent
shared membera copy for each usestill shared
Object.is(result, input)false for containerstrue

parse is the reader for a value coming in — from JSON, from a protocol frame — where a value built to the schema is what the caller wants next, and where dropping what the schema does not name is a feature.

validate is the reader for a value the caller already holds and must keep. A content-addressed document is the clear case: its bytes are its identity, so a reconstruction is a different document under a different hash, and "the payer did not report this figure" (absent) is not "the payer reported nothing here" (present, undefined). Answering "is this a T?" must not edit the thing being asked about. Against an open schema, validate also lets a consumer check the part it understands without discarding the part it does not.

Neither is more fundamental than the other: validate is not a parse whose result is thrown away. A third reader, the data form's validate, reads the same values against toData(s), the schema as plain data, and accepts exactly the DataJS values the other two do (a function or a symbol it may admit, see data-validate-admits-non-djs-values); validate/proof.f.mjs runs one acceptance table through all three. How the two schema-form readers share their code is in common's README.

A reader over a value that is already built cannot see how it was written. For reading straight from JSON text against a schema — one pass, no intermediate value, and a refusal of 1.00000000000000001 against a bigint — see rtti-parse.

Examples

The demo on this page runs the Built or handed back lesson: open({ name: string, age: number }) against {"name":"Alice","age":30,"admin":true}. Both readers accept it; parse's answer has no admin, and validate's is the value as typed. The lesson is defined in sample.f.mjs, and the module page's demo lists it among all the others.

Reference

  • TypeScript: a type is erased at run time, so a check like these is what narrowing needs at a boundary; validate's result is typed Ts<s>.
  • JSON Schema: validation answers yes or no, as validate does; nothing in it builds a value, as parse does.

Where it is implemented

parse, validate and the data form's validate in data, over the shared kernel in common.

Demo

Both readers accept the value. parse builds one with the declared keys only; validate hands back the value it was given, admin included.

open({ name: string, age: number })
open [] → {}open{}nameagestringnumber

TypeScript:

{readonly"age":number,readonly"name":string}

JSON Schema:

{"type":"object","properties":{"age":{"type":"number"},"name":{"type":"string"}},"required":["age","name"]}
Canonical form (toData)
export default [{},{"object":[{"props":{"age":{"number":true},"name":{"string":true}}}]}];

parse · ok

export default {"name":"Alice","age":30};

validate · ok

export default {"name":"Alice","age":30,"admin":true};

Emergent Testing

Idle. Press Run to start the suite.

  • ./proof.f.mjs