jt.toSchema
Declaration. Reconstructs a JSON Schema document from the canonical graph for a registered schema. Returns Record<string, unknown> | undefined - undefined when the schema is not registered. The reconstructed schema reflects the normalized canonical representation, which may differ slightly from the original authored schema.
Use this when you want to verify round-trip fidelity - that the canonical graph preserves all structural semantics from the authored schema. Also useful for producing normalized/canonical versions of schemas for display, debugging, or downstream tooling.
Don't use this when you need the original schema object as-authored (use jt.registry.get instead). get returns the original object reference; toSchema returns a new object reconstructed from the internal graph.
Examples
Example 1: Round-trip an Order schema
/**
* toSchema — Example 1: Round-trip an Order schema
* Demonstrates: reconstructed schema from the canonical graph, JSON-serializable
*
* bookstoreEntities.toSchema returns the Order schema as a plain object
* reconstructed from the internal canonical graph. It should contain the
* structural properties declared in OrderSchema.
*/
import {
bookstoreEntities, OrderSchema
} from '../bookstore/index.js';
const reconstructed = bookstoreEntities.toSchema(OrderSchema.$id);
console.assert(reconstructed !== undefined, 'Registered schema should return a value');
// Should be JSON-serializable without throwing
const serialized = JSON.stringify(reconstructed, null, 2);
console.assert(typeof serialized === 'string');
console.assert(serialized.length > 0);
// Reconstructed schema reflects the normalized canonical form
const rec = reconstructed as Record<string, unknown>;
console.assert('type' in rec, 'Reconstructed schema should have a type field');
// Show the reconstructed schema as produced from the canonical graph
console.log('reconstructed Order schema:', serialized);
Example 2: Verify a composed schema round-trips correctly
/**
* toSchema — Example 2: Verify a composed schema round-trips correctly
* Demonstrates: Compose.pick, jt.set, toSchema on a derived schema
*
* BookSchema is an allOf composition of BibliographicRecordSchema (isbn, title,
* authors, publishedOn) extended with retail fields (price, printStatus, etc.).
* isbn and title live on BibliographicRecordSchema, so BookSummarySchema is
* derived from BibliographicRecordSchema via Compose.pick. The picked schema is
* registered with jt.set, then reconstructed via toSchema to confirm it contains
* only the picked properties.
*/
import { Compose } from '../../../src/index.js';
import {
BibliographicRecordSchema,
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();
const BookSummarySchema = Compose.pick(
BibliographicRecordSchema,
[
'isbn',
'title',
'publishedOn'
] as const,
'https://bookstore.example/BookSummaryToSchema'
);
const jt2 = jt.set(BookSummarySchema);
const roundTripped = jt2.toSchema(BookSummarySchema.$id);
console.assert(roundTripped !== undefined, 'Composed schema should be retrievable');
const rec = roundTripped as Record<string, unknown>;
const props = Object.keys(rec.properties as Record<string, unknown>);
// Only the three picked properties should appear
console.assert(props.includes('isbn'), 'isbn should be in reconstructed properties');
console.assert(props.includes('title'), 'title should be in reconstructed properties');
console.assert(props.includes('publishedOn'), 'publishedOn should be in reconstructed properties');
console.assert(!props.includes('authors'), 'authors should not be in projected schema');
console.assert(!props.includes('inStock'), 'inStock should not be in projected schema');
// Show the reconstructed BookSummary schema with only the three picked properties
console.log('reconstructed BookSummary schema:', JSON.stringify(roundTripped, null, 2));
Example 3: Undefined for unregistered schemas
/**
* toSchema — Example 3: Returns undefined for unregistered schemas
* Demonstrates: toSchema returns undefined when schema ID is not in the registry
*
* A schema ID that was never registered produces undefined; no error is thrown.
*/
import {
bookstoreEntities
} from '../bookstore/index.js';
const missing = (bookstoreEntities.toSchema as (id: string) => Record<string, unknown> | undefined)('https://bookstore.example/NonExistent');
console.assert(missing === undefined, 'Unregistered schema should return undefined');
// Show the undefined return value for an unregistered schema ID
console.log('toSchema for unregistered ID:', missing);
Bad examples: what NOT to do
Anti-pattern 1: Using toSchema when you need the original authored object
/**
* toSchema — Anti-pattern 1: Using toSchema when you need the original authored object
* Demonstrates: toSchema reconstructs from graph; registry.get returns the original reference
*
* The anti-pattern uses toSchema for attribute extraction on the original
* authored schema; the correct approach is bookstoreEntities.registry.get.
*/
import {
BookSchema, bookstoreEntities
} from '../bookstore/index.js';
// Anti-pattern: toSchema reconstructs from the graph; key order or $defs may differ
// Don't do this for retrieving the original authored object
const reconstructed = bookstoreEntities.toSchema(BookSchema.$id);
if (reconstructed !== undefined) {
const _reconstructedTitle = reconstructed.title;
void _reconstructedTitle;
}
// Correct approach: use bookstoreEntities.registry.get to retrieve original reference
const original = bookstoreEntities.registry.get(BookSchema.$id);
console.assert(original !== undefined, 'BookSchema should be registered');
// original is the exact object reference passed to JsonTology.create
console.assert(original === BookSchema, 'registry.get returns the original schema reference');
// Show that registry.get returns the original $id unchanged
console.log('original.$id:', original?.$id);
console.log('registry.get === BookSchema:', original === BookSchema);
Anti-pattern 2: Using the reconstructed schema as a source of truth for structural comparison
/**
* toSchema — Anti-pattern 2: Using reconstructed schema for structural comparison
* Demonstrates: normalization may reorder keys; don't compare reconstructed vs original
*
* Deep-equal or JSON.stringify comparison between reconstructed and original
* may return false even when schemas are semantically equivalent. Use the
* original schema for structural comparisons.
*/
import {
aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';
// Anti-pattern: fragile comparison — normalization may reorder keys or inline $defs
// Don't do this
const reconstructed = bookstoreEntities.toSchema(OrderSchema.$id);
// JSON.stringify comparison may be false even for semantically equivalent schemas
const reconstructedStr = JSON.stringify(reconstructed);
const originalStr = JSON.stringify(OrderSchema);
// This comparison is not reliable — do not gate logic on it
void reconstructedStr;
void originalStr;
// Correct approach: use toSchema for debugging and display only
// Use the original schema or validate data for structural correctness
const order = bookstoreEntities.instantiate(OrderSchema, aboxFixtures.order);
console.assert(order.orderId === aboxFixtures.order.orderId);
// Show that the two JSON.stringify forms may differ (key order is not guaranteed)
console.log('reconstructed keys match original?', reconstructedStr === originalStr);
// Validate data against the original schema instead of comparing reconstructed forms
console.log('order.orderId (validated):', order.orderId);
Anti-pattern 3: Calling toSchema for an unregistered schema without handling undefined
/**
* toSchema — Anti-pattern 3: Calling toSchema for unregistered schema without undefined check
* Demonstrates: always guard against undefined before accessing the result
*
* Accessing .properties on an undefined return value throws a TypeError.
* The correct approach checks for undefined first.
*/
import {
bookstoreEntities
} from '../bookstore/index.js';
// Correct approach: check for undefined before accessing the result
const safeSchema = (bookstoreEntities.toSchema as (id: string) => Record<string, unknown> | undefined)('https://bookstore.example/Nonexistent');
// Anti-pattern: skipping the undefined check before accessing .properties
// const props = Object.keys(safeSchema.properties ?? {});
// ^ TypeError if safeSchema is undefined
if (safeSchema === undefined) {
// Schema not registered — handle gracefully
console.assert(true, 'Correctly handled undefined for unregistered schema');
} else {
// safeSchema is Record<string, unknown> here — no cast needed
const propsObj = safeSchema.properties as Record<string, unknown>;
const props = Object.keys(propsObj);
void props;
}
console.assert(safeSchema === undefined);
// Show that toSchema for an unregistered ID returns undefined (no throw)
console.log('safeSchema for unregistered ID:', safeSchema);
Comparison
jt.toSchema('https://bookstore.example/Book')
// Reconstructed from internal canonical graph// Not directly supported - no JSON Schema reconstruction from Zod's runtime representation.
// Use zodToJsonSchema (third-party) to export JSON Schema.// Use the `@valibot/to-json-schema` companion library:
import { toJsonSchema } from '@valibot/to-json-schema';
const jsonSchema = toJsonSchema(BookSchema);
// Limitation: produces JSON Schema from Valibot's runtime representation;
// no first-class graph reconstruction, and not all Valibot constructs map.// Limitation: io-ts has no built-in JSON Schema export. Codecs are
// runtime values without a structural representation that maps cleanly
// onto JSON Schema; third-party tooling (io-ts-types, io-ts-codegen) can
// emit specific subsets but full round-trip is not supported.// TypeBox schemas ARE plain JSON Schema - no reconstruction needed.
// JSON.stringify(BookSchema) gives the schema directly.// Not directly supported - AJV stores compiled validators, not schemas.
// Pass schema directly: JSON.stringify(bookSchema)Book.model_json_schema() # Exports JSON Schema from the model class// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.Related
jt.registry.get- retrieve the original schema object (not reconstructed)- Ontology and Graphs -
toQuadsandfromQuadsfor the advanced graph API
See also
- Bookstore domain - where all schemas are defined
- Schemas guide - schema registration and management