Skip to content

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.

ts
const CustomerSchema = {
  $id: 'urn:bookstore:Customer',
  type: 'object',
  properties: {
    id:    { $ref: 'urn:bookstore:CustomerId' },
    email: { $ref: 'urn:bookstore:Email' },
    name:  { $ref: 'urn:bookstore:PersonName' }
  },
  required: ['id', 'email', 'name']
} as const;

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 draft-2020-12 JSON Schema (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

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.

LibraryTS typeRuntime validatorWire-format JSON SchemaOWL/SHACL output
Zodsourceyesexport-only via zod-to-json-schema (lossy)no
TypeBoxsource (via Static<>)yes (Value.Check)yes (TypeBox schemas ARE JSON Schema)no
Valibotsourceyesexport-only via @valibot/to-json-schemano
Pydanticsourceyesexport-only via model_json_schema()no
Ajvno TS inferenceyessource (raw JSON Schema author)no
json-tologyderived from sourceyessourceyes (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.

Loading graph data...

Read the full guide


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.

ts
import { JsonTology } from 'json-tology';
import type { InferType } from 'json-tology/types';

const CustomerSchema = {
  $id: 'https://bookstore.example/Customer',
  type: 'object',
  properties: {
    id:        { type: 'string', format: 'uuid' },
    email:     { type: 'string', format: 'email' },
    name:      { type: 'string' },
    addresses: { type: 'array', items: { type: 'object' }, default: [] },
  },
  required: ['id', 'email', 'name'],
} as const;

type Customer = InferType<typeof CustomerSchema>;
//   ^? { readonly id: string; readonly email: string; readonly name: string; readonly addresses?: readonly object[] }

const jt = JsonTology.create({
  baseIRI: 'https://bookstore.example',
  schemas: [CustomerSchema] as const,
});

const customer = jt.instantiate(CustomerSchema.$id, {
  id: 'c1a2b3d4-e5f6-7890-abcd-ef1234567890',
  email: 'alice@bookstore.example',
  name: 'Alice Chen',
});
//    ^? Customer - typed, validated, defaults applied

That's the entire core. Validation, type inference, coercion, defaults - all from one schema literal.

What's in the box

You getWithout 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.

See also

Released under the MIT License.