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)`);
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}`);
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)`);
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)`);
Brand error types
The named error brand types live in src/types/TypeErrors.ts alongside the Compose argument validation brands:
| Type | Signals |
|---|---|
RequiredKeyNotInPropertiesInterface | A required entry names a key absent from properties |
DependentRequiredKeyNotInPropertiesInterface | A dependentRequired key or dependent entry names a key absent from properties |
IfDiscriminatorNotInPropertiesInterface | An 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)`);
Related
- Compose argument validation - pick/omit/subClassOf/discriminatedUnion argument type checking
- Constraint brands - keyword-level phantom brands
- Validation modes - enforcement layer reference