Skip to content

Ontology and Graphs

Validation modes: Validation modes reference

Conformance status: 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 References - Standards conformance. File issues at github.com/Studnicky/json-tology/issues.

You only need this section if you want to emit or consume RDF/OWL/SHACL output, perform graph-based reasoning, or round-trip data through an RDF store. If you are building a TypeScript application that validates and coerces data, the core guides (Schemas, Validation, Composition, Transforms) are all you need. The ontology features are fully tree-shakable - importing from json-tology/ontology does not increase the bundle of consumers who only import from json-tology, json-tology/value, or json-tology/types.

The bookstore schemas defined in the Bookstore Domain are used throughout these examples. The same canonical graph used for validation is the source of truth for all ontology output - there is no second semantic model.


jt.toTbox

Declaration. Returns a fresh OntologyBuilder containing only the OWL TBox derived from all registered schemas - class declarations, property declarations, domain and range assertions, and cardinality constraints. No SHACL shapes are included. Not cached - every call builds a new OntologyBuilder.

Use this when you need only the OWL TBox (class/property vocabulary) without SHACL structural constraints - for example, when loading class definitions into an OWL reasoner, publishing a schema as Linked Data vocabulary, or merging TBox quads into an existing knowledge graph without overwriting separately managed shape constraints.

Don't use this when you want both TBox and SHACL in a single document - use ontology() for that. Don't use it when you only want structural validation shapes - use toShacl().

Examples

Example 1: Generate OWL TBox JSON-LD from bookstore schemas

/**
 * ontology — combined TBox + SHACL via ontology().
 *
 * ontology() is the combined, cached method: TBox classes/properties plus
 * SHACL shapes in a single OntologyBuilder. Use toTbox() or toShacl() when
 * you need only one vocabulary. Use ontology() when you need both.
 */

import { bookstoreEntities } from '../bookstore/index.js';

const builder = bookstoreEntities.ontology();

// OWL JSON-LD (TBox)
const owlJsonLd = builder.jsonLd();

// OWL JSON-LD as a JS object
const owl = builder.jsonLdObject();

// SHACL shapes
const shacl = builder.shaclObject();

// Prefix map
const ctx = builder.context();

// Subsequent calls return the same cached builder
const cached = bookstoreEntities.ontology();

// true — ontology() is cached
console.assert(builder === cached, 'ontology() is cached');
console.log('ontology() cached:', builder === cached);

const owlGraph = (owl as { '@graph'?: unknown[] })['@graph'];
const shaclGraph = (shacl as { '@graph'?: unknown[] })['@graph'];

console.log('TBox classes + properties in @graph:', Array.isArray(owlGraph) ? owlGraph.length : 0);
console.log('SHACL shapes in @graph:', Array.isArray(shaclGraph) ? shaclGraph.length : 0);
console.log('JSON-LD byte length:', owlJsonLd.length);
console.log('owl: prefix:', ctx.owl);
Output
Press Execute to run this example against the real library.

Example 2: Merge TBox with separately sourced ABox

/**
 * Merge TBox with separately sourced ABox
 *
 * Use toTbox() to get the OWL vocabulary, then combine with separately
 * produced ABox quads (from toQuads or an external reasoner).
 */

import {
  bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';

const tbox = bookstoreEntities.toTbox();
const abox = bookstoreEntities.toQuads(CustomerSchema, {
  'addresses': [{
    'city': 'München',
    'postalCode': '80538',
    'street': 'Reichenbachstraße 14'
  }],
  'customerId': 'c1a2b3d4-e5f6-7890-abcd-ef1234567890',
  'email': 'bastian.bux@bookstore.example',
  'name': 'Bastian Balthazar Bux'
});

// OWL class/property declarations
// ABox individual assertions (already a QuadInterface[])
const merged = {
  '@context': tbox.context(),
  '@graph': [
    ...(tbox.jsonLdObject()['@graph'] as unknown[]),
    ...abox
  ]
};

console.assert(Boolean(merged['@context']), 'context present');
console.assert(merged['@graph'].length > 0, 'graph has triples');

console.log('Merged @graph count:', merged['@graph'].length);
console.log('TBox quads count:', tbox.quads().length);
console.log('ABox quads count:', abox.length);
Output
Press Execute to run this example against the real library.

Bad examples

/**
 * toTbox() is not cached — each call returns a fresh OntologyBuilder.
 *
 * `toTbox()` builds a new `OntologyBuilder` on every call. Reference equality
 * between two calls is `false`. For hot paths where reference stability matters,
 * use `ontology()` which is cached and returns the same builder until a new
 * schema is registered.
 *
 * Demonstrates: toTbox() !== toTbox() (two fresh builders); ontology() returns
 * the same cached reference on repeated calls.
 */

import { bookstoreEntities } from '../bookstore/index.js';

// toTbox() — not cached: each call is fresh
const first = bookstoreEntities.toTbox();
const second = bookstoreEntities.toTbox();

console.assert(first !== second, 'toTbox() returns a new OntologyBuilder on each call');
console.log('toTbox() not cached (first !== second):', first !== second);

// ontology() — cached: same reference until a new schema is registered
const cachedA = bookstoreEntities.ontology();
const cachedB = bookstoreEntities.ontology();

console.assert(cachedA === cachedB, 'ontology() returns the same cached OntologyBuilder');
console.log('ontology() cached (cachedA === cachedB):', cachedA === cachedB);
Output
Press Execute to run this example against the real library.

Comparison

ToolOWL TBox generation
json-tology toTbox()Full OWL vocabulary from registered JSON Schemas
TypeBoxNo ontology output
ZodNo ontology output
AJVNo ontology output
Pydantic model_json_schema()JSON Schema only - no OWL/SHACL

See also


jt.toShacl

Declaration. Returns a fresh OntologyBuilder containing only the SHACL shapes derived from all registered schemas - node shapes and property shapes encoding structural constraints. No OWL class or property declarations are included. Not cached - every call builds a new OntologyBuilder.

Use this when you need only the SHACL shapes - for example, when validating RDF data in a SHACL processor, publishing shapes for a shared API contract, or loading shapes into a triplestore that manages its own TBox separately.

Don't use this when you want both TBox and SHACL - use ontology(). Don't use it when you only need OWL class vocabulary - use toTbox().

Examples

Example 1: Generate SHACL shapes JSON-LD from bookstore schemas

/**
 * Generate SHACL shapes JSON-LD from bookstore schemas
 *
 * toShacl() returns a fresh OntologyBuilder containing only SHACL shapes
 * without OWL class/property declarations.
 */

import { bookstoreEntities } from '../bookstore/index.js';

const shaclBuilder = bookstoreEntities.toShacl();

// SHACL shapes JSON-LD object (includes sh: prefix in context)
const shacl = shaclBuilder.shaclObject();

// Prefix map
const ctx = shaclBuilder.context();

console.assert(Boolean(shacl), 'shacl object present');
console.assert(Boolean(ctx.sh), 'sh prefix defined');

const shaclGraph = (shacl as { '@graph'?: unknown[] })['@graph'];

console.log('SHACL shapes in @graph:', Array.isArray(shaclGraph) ? shaclGraph.length : 0);
console.log('sh: prefix:', ctx.sh);
Output
Press Execute to run this example against the real library.

Example 2: SHACL-only export for a validation pipeline

/**
 * SHACL-only export for a validation pipeline
 *
 * When you only need SHACL shapes (no OWL), ship the shapes to a downstream
 * SHACL processor without OWL vocabulary leakage.
 */

import {
  BookSchema, bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';

// Ship SHACL to a downstream SHACL processor — no OWL leakage
const shapes = bookstoreEntities.toShacl().shaclObject();

console.assert(Boolean(shapes), 'shacl shapes present');
console.assert(Boolean(BookSchema.$id), 'book schema registered');
console.assert(Boolean(CustomerSchema.$id), 'customer schema registered');

const shaclGraph = (shapes as { '@graph'?: unknown[] })['@graph'];

console.log('SHACL shapes exported:', Array.isArray(shaclGraph) ? shaclGraph.length : 0);
console.log('BookSchema registered at:', BookSchema.$id);
console.log('CustomerSchema registered at:', CustomerSchema.$id);
Output
Press Execute to run this example against the real library.

Bad examples

/**
 * toShacl() — shaclObject() not quads().
 *
 * `toShacl()` returns an `OntologyBuilder` containing only SHACL shapes.
 * For this builder `quads()` is empty — SHACL content lives in `shaclObject()`.
 * Calling `quads()` on a `toShacl()` result always returns an empty array.
 *
 * Demonstrates: quads() is empty for toShacl(); shaclObject() contains content.
 */

import { bookstoreEntities } from '../bookstore/index.js';

const shaclBuilder = bookstoreEntities.toShacl();

// quads() is always empty for a toShacl() builder — SHACL lives in shaclObject()
const owlQuads = shaclBuilder.quads();

console.assert(
  owlQuads.length === 0,
  'toShacl() quads() is empty — SHACL lives in shaclObject()'
);

// shaclObject() contains the SHACL shapes
const shaclContent = shaclBuilder.shaclObject();

console.assert(typeof shaclContent === 'object', 'shaclObject() returns an object');

const graphArray = (shaclContent as { '@graph'?: unknown[] })['@graph'];

console.assert(
  Array.isArray(graphArray) && graphArray.length > 0,
  'shaclObject() @graph contains SHACL node shapes'
);

console.log('toShacl() quads() length:', owlQuads.length);
console.log('shaclObject() @graph length:', Array.isArray(graphArray) ? graphArray.length : 0);
Output
Press Execute to run this example against the real library.

Comparison

ToolSHACL shapes generation
json-tology toShacl()Full SHACL node/property shapes from JSON Schemas
TypeBoxNo SHACL output
ZodNo SHACL output
AJVNo SHACL output
Pydantic model_json_schema()JSON Schema only - no OWL/SHACL

See also


jt.validateWithShacl

Declaration. Inverse of toShacl(): validate ABox data quads against SHACL shape quads. Accepts the OntologyBuilder returned by toShacl() directly, or a raw QuadInterface[] shape array. Returns a ShaclValidationReportInterface with conforms and results.

See SHACL validation for the full reference: result shape, constraint components covered, and runnable examples.


jt.ontology

Declaration. Returns an OntologyBuilder derived from all registered schemas containing both the OWL TBox and SHACL shapes. The result is cached - subsequent calls return the same builder until a new schema is registered. The OntologyBuilder exposes methods for JSON-LD, SHACL, raw quads, and the prefix context.

Use this when you need both TBox output (class definitions, property declarations, domain/range assertions) and SHACL shapes from your schemas in a single artifact - for use in an OWL reasoner, a semantic knowledge graph, or an API that consumes JSON-LD.

Don't use this when you need only one vocabulary - use toTbox() for OWL only or toShacl() for SHACL only. The separation avoids coupling two concerns when a consumer only needs one.

Examples

Example 1: Generate OWL JSON-LD for all bookstore schemas

/**
 * Generate OWL JSON-LD for all bookstore schemas
 *
 * ontology() returns a cached OntologyBuilder containing both OWL TBox
 * and SHACL shapes in one document.
 */

import { bookstoreEntities } from '../bookstore/index.js';

const builder = bookstoreEntities.ontology();

// OWL JSON-LD string
const owlJson = builder.jsonLd();

console.log('OWL JSON-LD (first 60 chars):', owlJson.slice(0, 60));

// OWL JSON-LD as a JS object
const owl = builder.jsonLdObject();

// SHACL shapes JSON-LD
const shacl = builder.shaclObject();

// Prefix map
const ctx = builder.context();

console.assert(ctx.owl === 'http://www.w3.org/2002/07/owl#', 'owl prefix correct');

const owlGraph = (owl as { '@graph'?: unknown[] })['@graph'];
const shaclGraph = (shacl as { '@graph'?: unknown[] })['@graph'];

console.log('TBox @graph node count:', Array.isArray(owlGraph) ? owlGraph.length : 0);
console.log('SHACL @graph node count:', Array.isArray(shaclGraph) ? shaclGraph.length : 0);
console.log('owl: prefix:', ctx.owl);
Output
Press Execute to run this example against the real library.

Example 2: OWL and SHACL from cross-referenced schemas

CustomerSchema has addresses: [Address] via $ref. The ontology output produces rdfs:domain and rdfs:range relations between the Customer class and the Address class.

/**
 * OWL and SHACL from cross-referenced schemas
 *
 * When CustomerSchema has addresses: [Address] via $ref, the ontology output
 * produces rdfs:domain and rdfs:range relations between the Customer class
 * and the Address class.
 */

import {
  AddressSchema, bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';

const owl = bookstoreEntities.ontology().jsonLdObject();
const shacl = bookstoreEntities.ontology().shaclObject();

console.assert(Boolean(AddressSchema.$id), 'address schema registered');
console.assert(Boolean(CustomerSchema.$id), 'customer schema registered');
console.assert(Boolean(owl), 'owl object present');
console.assert(Boolean(shacl), 'shacl object present');

const owlGraph = (owl as { '@graph'?: unknown[] })['@graph'];
const owlNodes = Array.isArray(owlGraph) ? owlGraph : [];
// Find Address and Customer class nodes in the TBox
const addressNode = owlNodes.find((n) => {
  return (n as Record<string, unknown>)['@id'] === AddressSchema.$id;
});
const customerNode = owlNodes.find((n) => {
  return (n as Record<string, unknown>)['@id'] === CustomerSchema.$id;
});

console.log('AddressSchema IRI:', AddressSchema.$id);
console.log('CustomerSchema IRI:', CustomerSchema.$id);
console.log('Address class in TBox:', addressNode !== undefined);
console.log('Customer class in TBox:', customerNode !== undefined);
console.log('TBox total node count:', owlNodes.length);
Output
Press Execute to run this example against the real library.

jt.toQuads

Declaration. Projects instance data into RDF quads (ABox individuals) and returns a QuadInterface[] array of the projected nodes. Validates the data against the schema before projecting - throws MaterializationError if validation fails. Inverse of fromQuads. See RDF round-trip for the full quad pattern.

Use this when you want to produce ABox (instance-level) RDF triples from validated domain objects - for storage in an RDF triplestore, for input to a reasoner, or for export as Linked Data.

Examples

Example 1: Project a customer to ABox quads

/**
 * Project a customer to ABox quads
 *
 * toQuads returns QuadInterface[] — use the array directly to emit RDF
 * individuals for storage or reasoner input.
 */

import {
  bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';

// toQuads returns QuadInterface[] — use the array directly
const abox = bookstoreEntities.toQuads(CustomerSchema, {
  'addresses': [{
    'city': 'München',
    'postalCode': '80538',
    'street': 'Reichenbachstraße 14'
  }],
  'customerId': 'c1a2b3d4-e5f6-7890-abcd-ef1234567890',
  'email': 'bastian.bux@bookstore.example',
  'name': 'Bastian Balthazar Bux'
});

// abox is QuadInterface[] — iterate, filter, or pass to OntologyBuilder
console.assert(abox.length > 0, 'quads generated');
console.assert(Boolean(abox[0]), 'first quad present');

console.log('ABox quad count:', abox.length);
console.log('First quad subject:', abox[0]?.subject.value);
const predicates = [...new Set(abox.map((quad) => {
  return quad.predicate.value;
}))].sort();

console.log('Predicates emitted:', predicates.join(', '));
Output
Press Execute to run this example against the real library.

Example 2: Combine TBox and ABox

/**
 * Combine TBox and ABox
 *
 * Merge OWL TBox vocabulary with ABox individual data to create
 * a complete RDF document.
 */

import {
  bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';

// TBox — OntologyBuilder
const tbox = bookstoreEntities.ontology();
// ABox — QuadInterface[]
const abox = bookstoreEntities.toQuads(CustomerSchema, {
  'addresses': [{
    'city': 'München',
    'postalCode': '80538',
    'street': 'Reichenbachstraße 14'
  }],
  'customerId': 'c1a2b3d4-e5f6-7890-abcd-ef1234567890',
  'email': 'bastian.bux@bookstore.example',
  'name': 'Bastian Balthazar Bux'
});

// Merge for a complete JSON-LD document:
// OWL/SHACL quads from the OntologyBuilder
// ABox individual quads (QuadInterface[] — spread directly)
const merged = {
  '@context': tbox.context(),
  '@graph': [
    ...(tbox.jsonLdObject()['@graph'] as unknown[]),
    ...abox
  ]
};

console.assert(Boolean(merged['@context']), 'context present');
console.assert(merged['@graph'].length > 0, 'graph has content');

console.log('Merged @graph node count:', merged['@graph'].length);
console.log('ABox quads count:', abox.length);
const ctxKeys = Object.keys(tbox.context());

console.log('Context prefix count:', ctxKeys.length);
Output
Press Execute to run this example against the real library.

jt.fromQuads

Declaration. Lifts RDF quads back into typed JS objects. Inverse of toQuads. Given quads produced by toQuads, a reasoning engine, or any RDF source, recovers plain JS objects matching the target schema. Each returned object is validated through instantiate to apply defaults, transforms, and type safety. Returns an array of the schema's inferred type (Array<ParseOutputType<TRefs[K], TRefs>>).

Use this when you have RDF quads from an external source (a triplestore query result, a reasoner output) and need to recover validated domain objects.

Examples

Example 1: Round-trip a customer through quads

/**
 * Round-trip a customer through quads
 *
 * Use fromQuads to lift RDF quads back into typed JS objects.
 * Inverse of toQuads.
 */

import {
  bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';

const customerData = {
  'addresses': [{
    'city': 'München',
    'postalCode': '80538',
    'street': 'Reichenbachstraße 14'
  }],
  'customerId': 'c1a2b3d4-e5f6-7890-abcd-ef1234567890',
  'email': 'bastian.bux@bookstore.example',
  'name': 'Bastian Balthazar Bux'
};

// Project to quads — toQuads returns QuadInterface[] directly
const abox = bookstoreEntities.toQuads(CustomerSchema, customerData);

// Lift back to typed objects — use the string key form for full type inference
const customers = bookstoreEntities.fromQuads(CustomerSchema.$id, abox);
// customers: Customer[] — each element validated through coerce

console.assert(Array.isArray(customers), 'customers is array');
console.assert(customers.length > 0, 'at least one customer');

const first = customers[0];

if (first === undefined) {
  throw new Error('expected at least one customer');
}

console.assert(first.name === 'Bastian Balthazar Bux', 'name preserved');

console.log('Round-trip: toQuads quad count:', abox.length);
console.log('Round-trip: fromQuads customer count:', customers.length);
console.log('Recovered customer name:', first.name);
Output
Press Execute to run this example against the real library.

jt.toSchema

See jt.toSchema in the Serialization guide - it reconstructs a JSON Schema from the canonical graph and is useful for verifying round-trip fidelity, but is not specific to the RDF/ontology use case.


OntologyBuilder.addFromJsonLd / addShaclFromJsonLd

Re-ingest a JSON-LD object into a fresh OntologyBuilder via addFromJsonLd (for TBox quads) or addShaclFromJsonLd (for SHACL quads). Both are async — they call jsonld.toRDF internally and append the resulting quads to the canonical store.

/**
 * OntologyBuilder.addFromJsonLd / addShaclFromJsonLd — async JSON-LD ingestion.
 *
 * `addFromJsonLd` parses a JSON-LD document via `jsonld.toRDF` and appends the
 * resulting quads to the canonical ontology store. `addShaclFromJsonLd` does
 * the same for the SHACL store.
 *
 * This example shows a round-trip:
 *   1. Export the bookstore TBox to a JSON-LD object via `toTbox().jsonLdObject()`.
 *   2. Feed that object into a fresh `OntologyBuilder` via `addFromJsonLd`.
 *   3. Assert that all stable (fully-named) quads round-trip faithfully.
 *      Blank node IRIs (starting with "_:") are re-assigned on each JSON-LD
 *      parse, so they are excluded from the parity check.
 */

import { OntologyBuilder } from '../../../src/index.js';
import { bookstoreEntities } from '../bookstore/index.js';

// Step 1 — obtain the canonical TBox JSON-LD object.
const tboxBuilder = bookstoreEntities.toTbox();
const originalQuads = tboxBuilder.quads();
const tboxJsonLdObject = tboxBuilder.jsonLdObject();

console.assert(
  originalQuads.length > 0,
  'toTbox() must produce at least one quad'
);

// Step 2 — re-ingest the JSON-LD object into a fresh OntologyBuilder.
const freshBuilder = new OntologyBuilder({
  'baseIri': 'https://bookstore.example',
  'prefixes': tboxBuilder.context()
});

await freshBuilder.addFromJsonLd(tboxJsonLdObject);

const reingested = freshBuilder.quads();

console.assert(
  reingested.length > 0,
  'Re-ingested builder must contain at least one quad'
);

// Step 3 — stable-quad parity.
// Blank node IRIs (starting with "_:") are re-assigned on each JSON-LD parse;
// only quads whose subject AND object are both named IRIs (or literals) are
// stable across round-trips and included in the parity check.
function isBlankNode(value: string): boolean {
  return value.startsWith('_:');
}

const reingestedStableKeys = reingested
  .filter((quad) => {
    return !isBlankNode(quad.subject.value) && !isBlankNode(quad.object.value);
  })
  .map((quad) => {
    return `${quad.subject.value}|${quad.predicate.value}|${quad.object.value}`;
  });
const reingestedStableSet = new Set(reingestedStableKeys);

const stableOriginalQuads = originalQuads.filter((quad) => {
  return !isBlankNode(quad.subject.value) && !isBlankNode(quad.object.value);
});

const missingStableCount = stableOriginalQuads.filter((quad) => {
  return !reingestedStableSet.has(`${quad.subject.value}|${quad.predicate.value}|${quad.object.value}`);
}).length;

console.assert(
  missingStableCount === 0,
  `All stable (fully-named) quads must round-trip; missing: ${missingStableCount}`
);

console.log('original quad count:', originalQuads.length);
console.log('re-ingested quad count:', reingested.length);
console.log('stable quads checked:', stableOriginalQuads.length);
console.log('stable quad parity (missing):', missingStableCount);

// addShaclFromJsonLd — round-trip the SHACL shapes JSON-LD object.
// SHACL shapes use stable named IRIs so the full count should match.
const shaclBuilder = bookstoreEntities.toShacl();
const shaclJsonLdObject = shaclBuilder.shaclObject();
const originalShaclQuads = shaclBuilder.shaclQuads();

const freshShaclBuilder = new OntologyBuilder({
  'baseIri': 'https://bookstore.example',
  'prefixes': shaclBuilder.context()
});

await freshShaclBuilder.addShaclFromJsonLd(shaclJsonLdObject);

const reingestedShacl = freshShaclBuilder.shaclQuads();

console.assert(
  reingestedShacl.length === originalShaclQuads.length,
  `Re-ingested SHACL quad count (${reingestedShacl.length}) must equal original (${originalShaclQuads.length})`
);

console.log('original SHACL quad count:', originalShaclQuads.length);
console.log('re-ingested SHACL quad count:', reingestedShacl.length);
console.log('SHACL quad parity:', reingestedShacl.length === originalShaclQuads.length);
Output
Press Execute to run this example against the real library.

Direct serializer access

For advanced use cases without the JsonTology facade, serializers are importable from json-tology/ontology:

/**
 * Direct serializer access for advanced use cases
 *
 * When not using the JsonTology facade, serializers are directly importable
 * from json-tology/ontology.
 */

import {
  GraphOntologySerializer,
  GraphSchemaSerializer,
  GraphShaclSerializer,
  OntologyBuilder
} from '../../../src/ontology/index.js';
import { SchemaRegistry } from '../../../src/schema/index.js';
import { BookSchema } from '../bookstore/index.js';

const registry = new SchemaRegistry();

registry.set(BookSchema);

const graphs = registry.listGraphs();

// OWL
const owlSerializer = new GraphOntologySerializer();
const owlQuads = owlSerializer.serializeQuads(graphs);

const builder = new OntologyBuilder({
  'baseIri': 'https://bookstore.example',
  'prefixes': { 'bs': 'https://bookstore.example/' }
}).addFromQuads(owlQuads);
const owlJson = builder.jsonLd();

// SHACL
const shaclSerializer = new GraphShaclSerializer();
const shaclQuads = shaclSerializer.serializeQuads(graphs);

builder.addShaclFromQuads(shaclQuads);
const shaclJson = JSON.stringify(builder.shaclObject(), null, 2);

console.log('SHACL JSON-LD (first 60 chars):', shaclJson.slice(0, 60));

// Reconstruct schema from a single graph
const schemaSerializer = new GraphSchemaSerializer();
const graph = registry.graph(BookSchema.$id);

if (graph) {
  const schema = schemaSerializer.serialize(graph);

  console.assert(Boolean(schema), 'schema reconstructed');
  console.log('Schema reconstructed from graph:', schema.$id);
}

console.log('OWL quads count:', owlQuads.length);
console.log('OWL JSON-LD byte length:', owlJson.length);
console.log('SHACL quads count:', shaclQuads.length);
Output
Press Execute to run this example against the real library.

Custom prefixes and vocabulary plugins

/**
 * Custom prefixes and vocabulary plugins
 *
 * Define custom vocabulary plugins to emit domain-specific quads
 * beyond the core OWL/SHACL vocabularies. Register the plugin prefix
 * via the `prefixes` option so it appears in the OntologyBuilder context.
 */

import { JsonTology } from '../../../src/index.js';
import type { VocabularyPluginInterface } from '../../../src/interfaces/index.js';
import { BookSchema } from '../bookstore/index.js';

const MY_NS = 'https://myorg.io/ns#';

const myVocabulary: VocabularyPluginInterface = {
  extractRelations() {
    return [];
  },
  'prefixes': { 'myns': MY_NS },
  project() {
    // Emit custom quads for non-core predicates
  }
};

const jt = JsonTology.create({
  'baseIri': 'https://bookstore.example',
  // prefixes must be declared here to appear in OntologyBuilder.context()
  'prefixes': { 'myns': MY_NS },
  'schemas': [BookSchema] as const,
  'vocabularies': [myVocabulary]
});

const ctx = jt.ontology().context();

console.assert(ctx.myns === MY_NS, 'custom prefix registered');
console.log('custom prefix in context:', ctx.myns);
Output
Press Execute to run this example against the real library.

Querying the TBox

Once you have a TBox, you can query it with SPARQL. The toTbox().jsonLd() output is a valid JSON-LD document that can be loaded into any RDF store.

sparql
# Find all subclasses of urn:bookstore:Customer
SELECT ?subclass WHERE {
  ?subclass rdfs:subClassOf <urn:bookstore:Customer> .
}

# Find all properties whose range is urn:bookstore:Isbn
SELECT ?property WHERE {
  ?property rdfs:range <urn:bookstore:Isbn> .
}

# Find all named primitives (classes that are not object schemas)
SELECT ?cls WHERE {
  ?cls a owl:Class .
  FILTER NOT EXISTS { ?cls rdfs:subClassOf ?parent . }
}

Usage with N3.js

/**
 * Bridging json-tology TBox output to an n3 Store
 *
 * The canonical bookstore TBox is emitted as JSON-LD by
 * `bookstoreEntities.toTbox().jsonLd()`. The n3 library parses
 * Turtle / N-Quads, not JSON-LD directly, so this example uses the
 * higher-level structural output: every TBox node carries `@id` and
 * `@type`. A consumer that wants to feed n3.Store can either:
 *   1. Convert JSON-LD to N-Quads first (e.g. via the `jsonld` library), or
 *   2. Iterate the structural output and call `DataFactory` directly.
 *
 * This example demonstrates option 2 — extract `(@id, rdf:type,
 * objectClass)` triples directly from the structured TBox.
 */

import {
  DataFactory, Store
} from 'n3';
import { bookstoreEntities } from '../bookstore/index.js';

const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';

const tbox = bookstoreEntities.toTbox().jsonLdObject()['@graph'] as Array<{ '@id'?: unknown;
  '@type'?: unknown }>;
const store = new Store();

for (const node of tbox) {
  const id = node['@id'];
  const type = node['@type'];

  if (typeof id !== 'string' || typeof type !== 'string') {
    continue;
  }
  const subject = DataFactory.namedNode(id);
  const predicate = DataFactory.namedNode(RDF_TYPE);
  const object = DataFactory.namedNode(type);

  store.addQuad(subject, predicate, object);
}

const predicate = DataFactory.namedNode(RDF_TYPE);
const classObject = DataFactory.namedNode('http://www.w3.org/2002/07/owl#Class');
const owlClasses = store.getQuads(null, predicate, classObject, null);

console.assert(owlClasses.length > 0, 'OWL classes found in N3 store');
console.log('OWL class triples added to N3 store:', owlClasses.length);
console.log('First class IRI:', owlClasses[0]?.subject.value);
Output
Press Execute to run this example against the real library.

See also

Released under the MIT License.