Skip to content

Compile-time schema validation Compile-time

json-tology validates schema structure at the TypeScript level so cross-keyword consistency errors are caught by the compiler, not discovered at runtime.

See Validation modes for the badge system used across this documentation.

ValidateSchemaType<T>

ValidateSchemaType<T> (in src/types/SchemaValidation.ts) is a compile-time type that resolves to T when the schema is internally consistent and to never when a cross-keyword violation is detected.

Apply it as an assignment constraint to opt in for hand-written schemas:

/**
 * Compile-time schema validation: ValidateSchemaType opt-in
 *
 * `ValidateSchemaType<T>` resolves to `T` when the schema is internally
 * consistent and to `never` when a cross-keyword violation is detected.
 * Assign the schema to a `ValidateSchemaType`-typed variable to opt in
 * for hand-written schemas.
 *
 * Schemas passed to `Compose.subClassOf`, `Compose.complementOf`,
 * `Compose.disjointWith`, and `Compose.extend` are validated automatically
 * — no manual `_check` variable needed.
 */

import type { ValidateSchemaType } from '../../../src/types/index.js';
import {
  bookstoreEntities, ReviewSchema
} from '../bookstore/index.js';

// ReviewSchema is internally consistent — all required keys are in properties.
// This assignment compiles cleanly.
const _check: ValidateSchemaType<typeof ReviewSchema> = ReviewSchema;

void _check;

// Runtime confirmation: valid review validates without errors.
import { aboxFixtures } from '../bookstore/index.js';

const errs = bookstoreEntities.validate(ReviewSchema.$id, aboxFixtures.review);

console.assert(errs.length === 0);

// Log: ValidateSchemaType accepted ReviewSchema (all required keys present in properties).
console.log('ValidateSchemaType<ReviewSchema> accepted — compile-time check passed');
console.log(`runtime validation errors: ${errs.length} (expected 0)`);
Output
Press Execute to run this example against the real library.

Schemas passed to Compose.subClassOf, Compose.complementOf, Compose.disjointWith, and Compose.extend are validated automatically - correct-by-construction without a manual _check variable.

Validated constraints

required key presence Compile-time

Every key in required must appear in properties. A required entry that references a non-existent property surfaces a RequiredKeyNotInPropertiesInterface brand error at the call site.

/**
 * Compile-time schema validation: required key presence
 *
 * Every key in `required` must appear in `properties`. A `required` entry
 * that references a non-existent property surfaces a
 * `RequiredKeyNotInPropertiesType` brand error at the call site.
 *
 * The IDE hover on a failing assignment shows the specific brand type and
 * the offending key rather than a generic "not assignable to never" message.
 *
 * This example demonstrates the valid case — all required keys in
 * `BookSchema` exist in its `properties` — so the compile-time check
 * passes and the assignment succeeds.
 */

import type { ValidateSchemaType } from '../../../src/types/index.js';
import {
  aboxFixtures, BookSchema, bookstoreEntities
} from '../bookstore/index.js';

// BookSchema: all required keys ('isbn', 'title', 'authors', 'price', 'inStock',
// 'printStatus', 'publishedOn', 'stockLevel') are declared in properties.
const _check: ValidateSchemaType<typeof BookSchema> = BookSchema;

void _check;

// Runtime: validate the canonical rare book fixture.
const errs = bookstoreEntities.validate(BookSchema.$id, aboxFixtures.rareBook);

console.assert(errs.length === 0);

const title: string = aboxFixtures.rareBook.title;

console.assert(title === 'Die unendliche Geschichte');

// Log: required-key check passed — all required keys exist in properties.
console.log('ValidateSchemaType<BookSchema> accepted — required keys present in properties');
console.log(`runtime validation errors: ${errs.length} (expected 0)`);
console.log(`book title: ${title}`);
Output
Press Execute to run this example against the real library.

dependentRequired key presence Compile-time

Every trigger key and every entry in the dependent key arrays in dependentRequired must appear in properties. Violations surface a DependentRequiredKeyNotInPropertiesInterface brand error.

/**
 * Compile-time schema validation: dependentRequired key presence
 *
 * Every trigger key and every entry in the dependent key arrays in
 * `dependentRequired` must appear in `properties`. Violations surface a
 * `DependentRequiredKeyNotInPropertiesType` brand error.
 *
 * This example demonstrates the valid case — a payment schema where
 * `billing_address` is required when `credit_card` is present, and both
 * keys are declared in `properties`.
 */

import type { ValidateSchemaType } from '../../../src/types/index.js';
import { JsonTology } from '../../../src/index.js';

const PaymentSchema = {
  '$id': 'urn:docs-compile-time-03:Payment',
  'dependentRequired': { 'credit_card': ['billing_address'] },
  'properties': {
    'amount': {
      'exclusiveMinimum': 0,
      'type': 'number'
    },
    'billing_address': { 'type': 'string' },
    'credit_card': { 'type': 'string' }
  },
  'required': ['amount'],
  'type': 'object'
} as const;

// Both `credit_card` and `billing_address` are in properties — compiles.
const _check: ValidateSchemaType<typeof PaymentSchema> = PaymentSchema;

void _check;

const jt = JsonTology.create({
  'baseIri': 'urn:docs-compile-time-03',
  // doc example with synthetic fixture schemas
  'enableStrictGraph': false,
  'schemas': [PaymentSchema] as const
});

// With credit_card — billing_address is required; omitting it fails.
const errsWithCard = jt.validate(PaymentSchema.$id, {
  'amount': 850,
  'credit_card': '4111-1111-1111-1111'
  // billing_address intentionally omitted
});

console.assert(errsWithCard.length > 0);

// Without credit_card — billing_address not required; passes.
const errsWithout = jt.validate(PaymentSchema.$id, { 'amount': 850 });

console.assert(errsWithout.length === 0);

// Log: dependentRequired compile-time check passed; runtime enforces the constraint.
console.log('ValidateSchemaType<PaymentSchema> accepted — dependentRequired keys in properties');
console.log(`credit_card without billing_address: ${errsWithCard.length} error(s) (expected >0)`);
console.log(`  violation: ${errsWithCard.items[0]?.message ?? '(none)'}`);
console.log(`amount only (no credit_card): ${errsWithout.length} error(s) (expected 0)`);
Output
Press Execute to run this example against the real library.

if.properties discriminator presence Compile-time

Every property key in if.properties must appear in the parent schema's properties. Discriminator keys that are absent from properties surface an IfDiscriminatorNotInPropertiesInterface brand error.

/**
 * Compile-time schema validation: if.properties discriminator presence
 *
 * Every property key in `if.properties` must appear in the parent schema's
 * `properties`. Discriminator keys absent from `properties` surface an
 * `IfDiscriminatorNotInPropertiesType` brand error.
 *
 * This example shows the valid case — a book schema that conditionally
 * requires `isbn` when `kind` is 'physical'. Both `kind` and `isbn` are
 * declared in `properties`.
 */

import type { ValidateSchemaType } from '../../../src/types/index.js';
import { JsonTology } from '../../../src/index.js';

const BookKindSchema = {
  '$id': 'urn:docs-compile-time-04:BookKind',
  'if': {
    'properties': { 'kind': { 'const': 'physical' } },
    'required': ['kind']
  },
  'properties': {
    'isbn': {
      'pattern': '^\\d{13}$',
      'type': 'string'
    },
    'kind': {
      'enum': [
        'physical',
        'digital'
      ],
      'type': 'string'
    },
    'title': { 'type': 'string' }
  },
  'required': [
    'kind',
    'title'
  ],
  'then': { 'required': ['isbn'] },
  'type': 'object'
} as const;

// `kind` is in properties — the if.properties discriminator compiles.
const _check: ValidateSchemaType<typeof BookKindSchema> = BookKindSchema;

void _check;

const jt = JsonTology.create({
  'baseIri': 'urn:docs-compile-time-04',
  // doc example with synthetic fixture schemas
  'enableStrictGraph': false,
  'schemas': [BookKindSchema] as const
});

// Physical book must have isbn.
const errsPhysicalNoIsbn = jt.validate(BookKindSchema.$id, {
  'kind': 'physical',
  'title': 'Die unendliche Geschichte'
});

console.assert(errsPhysicalNoIsbn.length > 0);

// Digital book — isbn not required.
const errsDigital = jt.validate(BookKindSchema.$id, {
  'kind': 'digital',
  'title': 'Die unendliche Geschichte'
});

console.assert(errsDigital.length === 0);

// Log: if.properties discriminator compile-time check passed; runtime enforces conditional.
console.log('ValidateSchemaType<BookKindSchema> accepted — if.properties keys present in properties');
console.log(`physical book without isbn: ${errsPhysicalNoIsbn.length} error(s) (expected >0)`);
console.log(`  violation: ${errsPhysicalNoIsbn.items[0]?.message ?? '(none)'}`);
console.log(`digital book without isbn: ${errsDigital.length} error(s) (expected 0)`);
Output
Press Execute to run this example against the real library.

Brand error types

The named error brand types live in src/types/TypeErrors.ts alongside the Compose argument validation brands:

TypeSignals
RequiredKeyNotInPropertiesInterfaceA required entry names a key absent from properties
DependentRequiredKeyNotInPropertiesInterfaceA dependentRequired key or dependent entry names a key absent from properties
IfDiscriminatorNotInPropertiesInterfaceAn if.properties key is absent from the parent properties

IDE hovers on a failing assignment show the specific brand type and the offending key rather than a generic "not assignable to never" message.

Compose integration

Compose methods that accept a schema body (subClassOf, complementOf, disjointWith, extend) apply ValidateSchemaType as a parameter constraint. This means any schema passed to these methods is validated automatically:

/**
 * Compile-time schema validation: Compose integration
 *
 * Compose methods that accept a schema body (`subClassOf`, `complementOf`,
 * `disjointWith`, `extend`) apply `ValidateSchemaType` as a parameter
 * constraint. Any schema passed to these methods is validated automatically —
 * no manual `_check` variable needed.
 *
 * This example uses `Compose.extend` on `BookSchema` to derive a featured
 * book variant. The body is internally consistent so the composition compiles.
 */

import { Compose } from '../../../src/index.js';
import {
  BookSchema,
  createBookstoreDocRegistry
} 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();

// FeaturedBook extends Book with an additional `featuredOn` property.
// The body has `featuredOn` in both properties and required — compiles.
// `Compose.extend` takes the parent schema, the additions body, and the
// new $id as three separate arguments. The $id must NOT appear inside
// the additions body — it is the third positional argument.
const FeaturedBookSchema = Compose.extend(
  BookSchema,
  {
    'properties': {
      'featuredOn': {
        'format': 'date',
        'type': 'string'
      }
    },
    'required': ['featuredOn'],
    'type': 'object'
  } as const,
  'urn:docs-compile-time-05:FeaturedBook'
);

// The $id is the third argument to Compose.extend; confirm it is a string.
console.assert(typeof FeaturedBookSchema.$id === 'string');

// The extended schema includes all required keys from both base and body.
const registry = jt.set(FeaturedBookSchema);
const errs = registry.validate(FeaturedBookSchema.$id, {
  'authors': ['Michael Ende'],
  'featuredOn': '2026-05-01',
  'inStock': true,
  'isbn': '9783522128001',
  'price': {
    'amount': 850,
    'currency': 'EUR'
  },
  'printStatus': 'outOfPrint',
  'publishedOn': '1979-09-01',
  'stockLevel': 5,
  'title': 'Die unendliche Geschichte'
});

console.assert(errs.length === 0);

// Log: Compose.extend applies ValidateSchemaType automatically; no manual _check needed.
console.log(`Compose.extend produced $id: ${FeaturedBookSchema.$id}`);
console.log(`featured book validation errors: ${errs.length} (expected 0)`);
Output
Press Execute to run this example against the real library.

Released under the MIT License.