fromJson
JSON (as toJson writes it) as a value of schema s: strings where it has dates become dates, pairs where it has a
map a map, arrays where it has a set a set, digits where it has a bigint a bigint; a union's first member it then
fits. What doesn't fit is left as it is, for the check to say so.
import { checkValue } from "#codebase/tcmts/core/schema"; import type { Schema } from "#codebase/tcmts/core/schema"; export function fromJson(s: Schema, v: unknown, defs: Schema[]): unknown { switch (s.t) { case "ref": return defs[s.id] ? fromJson(defs[s.id]!, v, defs) : v; case "date": return typeof v === "string" && !Number.isNaN(Date.parse(v)) ? new Date(v) : v; case "bigint": return typeof v === "string" && /^-?\d+$/.test(v) ? BigInt(v) : typeof v === "number" ? BigInt(Math.trunc(v)) : v; case "array": return Array.isArray(v) ? v.map((x) => fromJson(s.of, x, defs)) : v; case "set": return Array.isArray(v) ? new Set(v.map((x) => fromJson(s.of, x, defs))) : v; case "map": return Array.isArray(v) ? new Map( v.map((p) => Array.isArray(p) ? [fromJson(s.key, p[0], defs), fromJson(s.value, p[1], defs)] : p, ), ) : v && typeof v === "object" ? new Map( Object.entries(v).map(([k, x]) => [ fromJson(s.key, k, defs), fromJson(s.value, x, defs), ]), ) : v; case "tuple": return Array.isArray(v) ? v.map((x, i) => s.of[i] ? fromJson(s.of[i]!, x, defs) : s.rest ? fromJson(s.rest, x, defs) : x, ) : v; case "object": if (!v || typeof v !== "object" || Array.isArray(v)) return v; return Object.fromEntries( Object.entries(v).map(([k, x]) => { const p = s.props[k]; return [k, p ? fromJson(p.s, x, defs) : s.index ? fromJson(s.index, x, defs) : x]; }), ); case "union": { for (const m of s.of) { const c = fromJson(m, v, defs); if (checkValue(m, c, "row", defs) === undefined) return c; } return v; } default: return v; } }