json-tology
One source of truth for TypeScript types, runtime validation, coercion, and OWL ontology output.
Author in JSON Schema. Share with any backend. Reason over the graph.
Compile-time Types
InferType<typeof Schema> derives the TypeScript type from your as-const schema literal. No code generation, no separate type definition.
Runtime Validation
validate(), is(), errors() check data against a registered schema and return structured ValidationErrors with JSON Pointer paths.
Coercion with Defaults
coerce() validates, applies defaults, runs Transform decoders, and strips unknown properties in one pass. Throws a typed InstantiationError on failure.
Composition
extend, pick, omit, partial, required, intersection, discriminatedUnion: Pydantic-style derivations from base schemas, all type-safe.
Transforms and Brands
Per-field decode/encode pipelines via Transform, plus brand types for nominal typing. Round-trip wire format to decoded values with type safety.
Tree-Shakable
Sub-path exports - json-tology/value, json-tology/schema, json-tology/types - let users opt into only what they need. Graph and ontology are fully tree-shakable.
JSON Schema is the source, not an output
Most TypeScript validation libraries treat JSON Schema as a side-effect. You write Zod or TypeBox or Valibot, and toJSONSchema(...) is a one-way export. The exported schema is a snapshot, not a contract: regenerate it on every refactor, hope your back-end picks up the change, hope no one edits the JSON copy by hand.
json-tology inverts this. The JSON Schema literal is the schema. Type inference is derived from it. Validation reads it. Coercion reads it. The OWL TBox reads it. The same as const object you authored in TypeScript is also a wire-format-compatible JSON Schema document, ready to ship to any consumer in any language.
/**
* Landing page: JSON Schema is the source, not an output
*
* The canonical `CustomerSchema` is:
*
* - A TypeScript type via `InferType<typeof CustomerSchema>`
* - A runtime validator via `entities.validate(CustomerSchema, data)`
* - An OpenAPI 3.1 component (paste into `components.schemas.Customer`)
* - A JSON Schema draft 2020-12 document (any conforming validator reads it)
* - An OWL class (via `entities.toTbox()`)
* - A SHACL shape (via `entities.toShacl()`)
*
* The same `as const` object that types your TypeScript is also the
* wire-format contract for every consumer.
*/
import {
aboxFixtures, createBookstoreDocRegistry,
CustomerSchema
} from '../bookstore/index.js';
// createBookstoreDocRegistry seeds a permissive copy of the bookstore — docs examples extend
// it with ad-hoc demo schemas; strict-graph checking is intentionally off here.
const jt = createBookstoreDocRegistry();
// The schema literal is also the validator — no separate declaration file.
const errs = jt.validate(CustomerSchema, aboxFixtures.customer);
console.assert(errs.length === 0);
const schemaId: string = CustomerSchema.$id;
console.assert(schemaId === 'urn:bookstore:Customer');
// The same schema literal serializes to valid JSON Schema 2020-12.
const asJson = JSON.stringify(CustomerSchema);
console.assert(asJson.includes('"$id"'));
console.assert(asJson.includes('urn:bookstore:Customer'));
console.log('schema $id:', schemaId);
console.log('validate error count:', errs.length);
console.log('serialized includes $id:', asJson.includes('"$id"'));
That literal is:
- A TypeScript type via
InferType<typeof CustomerSchema> - A runtime validator via
entities.validate(CustomerSchema, data) - An OpenAPI 3.1 component (paste it into
components.schemas.Customer) - A JSON Schema draft 2020-12 document (Ajv, FastValidator, fastify-json-schema, any conforming validator reads it directly)
- An OWL class (via
entities.toTbox()) - A SHACL shape (via
entities.toShacl()) - A documentation source for tools like
@apidevtools/json-schema-ref-parser,redoc,swagger-ui
All examples in this documentation target the JSON Schema draft 2020-12 dialect.
Cross-language interop, no codegen
Sharing a contract with a Python back-end, a Go service, a Rust validator, or a Java reasoner is JSON.stringify(CustomerSchema). There is no generator step. There is no regeneration on every refactor. The TS type and the wire schema can't drift because they are the same object.
The same property holds for LLM consumers: every major model provider has converged on JSON Schema for structured outputs and tool calling, so a json-tology schema literal drops directly into an OpenAI or Anthropic function definition - see Sourcemeta's AI only speaks JSON Schema for the full argument. The same as const object that types your TypeScript also defines the model's output contract.
| Library | TS type | Runtime validator | Wire-format JSON Schema | OWL/SHACL output |
|---|---|---|---|---|
| Zod | source | yes | export-only via zod-to-json-schema (lossy) | no |
| TypeBox | source (via Static<>) | yes (Value.Check) | yes (TypeBox schemas ARE JSON Schema) | no |
| Valibot | source | yes | export-only via @valibot/to-json-schema | no |
| Pydantic | source | yes | export-only via model_json_schema() | no |
| Ajv | no TS inference | yes | source (raw JSON Schema author) | no |
| json-tology | derived from source | yes | source | yes (TBox + SHACL) |
TypeBox is the closest comparator: TypeBox schemas are also JSON-Schema-compatible objects. The differences: TypeBox doesn't ship runtime registration, cross-schema $ref resolution, ABox projection, OWL/SHACL output, or graph-native authoring tools.
Advanced usages
Your types are already a graph
Every TypeScript type system has a graph hiding in it. Below is the bookstore domain - six entities, eighteen primitives, every property a typed edge. Nodes are classes; edges are properties; arrowheads point from the domain entity to the range type.
Nodes
- Entity — registered top-level class (Customer, Order, Book, Review, …)
- Primitive — named scalar / constrained type (Email, Iso8601, Amount, …)
Edges
- subClassOf —
rdfs:subClassOf(taxonomic narrowing) - equivalentClass —
owl:equivalentClass(alias / structural identity) - range —
rdfs:range(property → typed target) - domain —
rdfs:domain(explicit override) - disjointWith —
owl:disjointWith(no shared instances) - complementOf —
owl:complementOf(negation) - restriction —
owl:Restriction(cardinality / hasValue / …)
Why json-tology
If you're coming from Pydantic, Zod, or TypeBox, json-tology gives you the same authoring ergonomics with JSON Schema as the source of truth - your schema works in TypeScript, in JSON Schema validators, in OpenAPI, in IDE auto-complete, and as a wire-format contract, all from one declaration.
/**
* Landing page: core workflow — schema → type → validate → instantiate
*
* The entire core in one file: declare a schema, derive the TypeScript type,
* create a registry, validate and instantiate. Bastian Balthazar Bux is the
* canonical bookstore customer (from Michael Ende's *The Neverending Story*).
*/
import { JsonTology } from '../../../src/index.js';
import type { InferType } from '../../../src/types/index.js';
const CustomerSchema = {
'$id': 'urn:landing-02:Customer',
'properties': {
'addresses': {
'default': [],
'items': { 'type': 'object' },
'type': 'array'
},
'email': {
'format': 'email',
'type': 'string'
},
'id': {
'format': 'uuid',
'type': 'string'
},
'name': { 'type': 'string' }
},
'required': [
'id',
'email',
'name'
],
'type': 'object'
} as const;
type Customer = InferType<typeof CustomerSchema>;
const jt = JsonTology.create({
'baseIri': 'urn:landing-02',
// doc example with synthetic fixture schemas
'enableStrictGraph': false,
'schemas': [CustomerSchema] as const
});
const customer: Customer = jt.instantiate(CustomerSchema.$id, {
'email': 'bastian.bux@bookstore.example',
'id': 'c1a2b3d4-e5f6-7890-abcd-ef1234567890',
'name': 'Bastian Balthazar Bux'
});
// Typed, validated, defaults applied.
console.assert(customer.name === 'Bastian Balthazar Bux');
console.assert(Array.isArray(customer.addresses));
console.log('customer.name:', customer.name);
console.log('customer.email:', customer.email);
console.log('customer.addresses (default):', JSON.stringify(customer.addresses));
That's the entire core. Validation, type inference, coercion, defaults - all from one schema literal.
What's in the box
| You get | Without paying for |
|---|---|
Type inference (InferType) | A separate type-definition language |
Runtime validation (validate, is) | A second schema for runtime checks |
Coercion + defaults (instantiate) | Manual mapping of input shapes |
Field aliasing (jt:alias) | Custom transform layers for renames |
Computed fields (jt:computed) | Post-processing pipelines |
Cross-field invariants (addInvariant) | Custom validation glue |
Serialization (dump, dumpJson) | A separate serializer |
Composition (extend, pick, omit, partial, required) | Hand-written derived schemas |
W3C / RDF / OWL / SHACL conformance is aspirational and a work in progress. Output loads into reasoners like Apache Jena and editors like Protege, but full normative conformance is still being built out. See the References page.
If you also need RDF/OWL/SHACL output, that's available as opt-in advanced features under the Ontology and Graphs section. The core type-system path doesn't pay for any of it - json-tology/value, json-tology/schema, and json-tology/types exclude the graph and ontology modules entirely.
Quick links
- Getting Started - install, define a schema, validate, instantiate
- Bookstore Domain - the running example domain used throughout the docs
- Validation -
instantiate,validate,is,subschemaAt - Error Views -
aggregate,report(RFC 7807) - Type Inference - how
InferTypeworks, reference maps, branded types - Composition - derive schemas from other schemas
- Serialization -
dump,dumpJson, Transform encoders - Ontology and Graphs - advanced: OWL TBox, SHACL shapes, JSON-LD, ABox projection
Related
- Getting Started - install, validate, instantiate in 5 minutes
- Bookstore domain - the running example domain used throughout
- Picking a method - instantiate vs validate vs is vs materialize
See also
- Argument conventions - universal SchemaRef, static counterparts
- Composition - derive schemas with extend, pick, omit
- Ontology and Graphs - advanced: OWL TBox, SHACL shapes, JSON-LD