Skip to content

Schemas

A model’s structure is given by ordinary JSON Schema, draft 2020-12. OURS adds nothing to it. What OURS does add is a restriction on which keywords a model’s schema may use, and a rule for where a $ref resolves.

The subset is deliberately small, so that every schema round-trips to a type system without surprises and a consumer can implement support for all of it.

Keywords
Identity $id, $schema, $ref, $defs, $comment
Annotation title, description, default, readOnly
Objects type, properties, required, additionalProperties, unevaluatedProperties
Arrays items, minItems, maxItems
Values enum, const, format, pattern, minLength, maxLength, minimum, maximum
Composition allOf, anyOf, oneOf

type is one of object, string, number, integer, boolean, array, null, or an array of those.

unevaluatedProperties is draft 2020-12’s counterpart for a schema that extends another through allOf. It is in the subset for that case, where additionalProperties does not do what a reader expects.

Keywords outside the list are not forbidden by any validator you will run, but a consumer generating code from your ontology is entitled to ignore them, so a constraint expressed only in one of them is a constraint you are not really publishing.

A schema is known by its $id, and a $ref resolves against the $id in scope where the reference was written. RFC 3986 applies unchanged, which has two consequences worth stating because they are easy to get wrong.

A nested $id establishes a new base. Everything beneath it resolves against that, not against the document it happens to sit in.

{
"$id": "https://api.example.org/schemas/person.schema.json",
"$defs": {
"address": {
"$id": "nested/address.json",
"properties": {
"country": { "$ref": "country.json" }
}
}
}
}

country.json resolves against https://api.example.org/schemas/nested/, not against the document root, so it names https://api.example.org/schemas/nested/country.json.

A relative reference resolves against the base’s directory, so a base carries only as far as its last slash. This is why a document’s own $id is not applied against itself: schemas/person.json resolved against schemas/person.json is schemas/schemas/person.json.

A $ref beginning with # is a pointer inside the current document and is the schema’s own business.

Relative $ids throughout a bundle are allowed. They resolve exactly as an absolute base would, against each other.

Every vocabulary is also published as a JSON Schema enumeration, so that a model can constrain a property to your codes using nothing but a $ref, and any validator enforces it.

The schema is served beside the vocabulary, with .schema.json in place of .json:

Vocabulary Schema
https://api.example.org/codes/invoice-status.json https://api.example.org/codes/invoice-status.schema.json
https://api.example.org/codes/invoice-status https://api.example.org/codes/invoice-status.schema.json

The codes become a oneOf of constants, each carrying its display text as a title, which is JSON Schema’s own way of labelling the members of an enumeration:

{
"$id": "https://api.example.org/codes/invoice-status.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$comment": "Derived from the vocabulary at https://api.example.org/codes/invoice-status",
"title": "ExampleOrg invoice status",
"description": "List of invoice status codes.",
"type": "string",
"oneOf": [
{ "const": "OPEN", "title": "Open" },
{ "const": "PAID", "title": "Paid" },
{ "const": "VOID", "title": "Void" },
{ "const": "UNKNOWN", "title": "Unknown" }
]
}

A code’s definition becomes that member’s description.

A vocabulary you reference rather than define has no codes, so its derived schema is type: "string" with no oneOf. That is the honest result: you have not published the code list, so the schema cannot constrain to it.

Binding a model property to a vocabulary is then an ordinary reference:

{
"$id": "https://api.example.org/schemas/Invoice.schema.json",
"type": "object",
"properties": {
"status": { "$ref": "https://api.example.org/codes/invoice-status.schema.json" }
}
}

vocabularySchemaFor and vocabularySchemaUrl derive both, so the files you serve and the ones a consumer expects cannot drift.