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 permitted subset
Section titled “The permitted subset”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.
Identity and references
Section titled “Identity and references”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.
Vocabularies as schemas
Section titled “Vocabularies as schemas”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.