Specification scope: Core only.
Delivery instructions do not belong in every address. Adding an instructions
property to the base type overstates that contract. Publishing another address
schema for one optional property creates a second contract to maintain.
JSON Structure calls this optional extension an add-in. The schema advertises
an abstract add-in through $offers. An instance that needs the extra contract
selects it through $uses.
Watch where $uses appears. It belongs to the JSON instance receiving the
extra members, not to the schema that advertises them.
The schema makes an offer
The base schema describes an ordinary street address. DeliveryInstructions
adds one property for producers that need it.
{
"$schema": "https://json-structure.org/meta/core/v0/#",
"$id": "https://schemas.example.com/street-address.json",
"$root": "#/definitions/StreetAddress",
"$offers": {
"DeliveryInstructions": "#/definitions/DeliveryInstructions"
},
"definitions": {
"StreetAddress": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" },
"zip": { "type": "string" }
},
"required": ["street", "city", "state", "zip"]
},
"DeliveryInstructions": {
"abstract": true,
"type": "object",
"$extends": "#/definitions/StreetAddress",
"properties": {
"instructions": { "type": "string" }
},
"required": ["instructions"]
}
}
}
At the schema root, $offers maps a public add-in name to a definition in the
same document. The declaration advertises DeliveryInstructions; it does not
apply it. Without an opt-in, the root type remains StreetAddress.
The add-in is abstract because it is not a second independently selectable
address type. In the add-in model, it extends the object it augments and is
injected into that type when selected.
The instance opts in
A normal instance does not mention the add-in:
{
"$schema": "https://schemas.example.com/street-address.json",
"street": "123 Main St",
"city": "Anytown",
"state": "WA",
"zip": "98101"
}
An instance that needs delivery instructions opts in:
{
"$schema": "https://schemas.example.com/street-address.json",
"$uses": ["DeliveryInstructions"],
"street": "123 Main St",
"city": "Anytown",
"state": "WA",
"zip": "98101",
"instructions": "Leave at the back door"
}
Here $schema identifies the address schema and $uses selects one of its
offers. $uses is a set of names, so an instance may select several compatible
add-ins.
A schema document may itself be an instance of a meta-schema, in which case
$uses applies at that level. In ordinary use, the
document receiving optional members carries $uses.
The unusual direction of $extends
$extends normally merges properties and constraints from abstract base types
into an extending object or tuple. A concrete derived type cannot redefine an
inherited property. Abstract bases also cannot be used directly as property
types or referenced through $ref.
An add-in points $extends in an unusual direction: the abstract add-in names
the concrete schema type it augments. When selected, the resulting composite
replaces that base type in the instance’s effective type model. The base schema
stays unchanged, while the instance records the optional contract it selected.
The current core draft contains two conflicting rules. The add-in section and
its normative example allow an abstract add-in to $extends a concrete type, as
DeliveryInstructions does above. The general $extends rules say that every
pointer targets an abstract type. Implementations need to recognize the add-in
case described by its dedicated section. My recommendation for the draft is an
explicit exception that makes the two rules agree.
No unadvertised add-ins
This mechanism is not open-ended inheritance. An instance may select only a
name advertised by the referenced schema’s $offers, or an allowed pointer in
the meta-schema case. The offered definition must already exist in the same
schema document.
The base type defines what every instance has. $offers publishes the supported
extensions, and $uses records what one instance activated. A consumer can
distinguish a declared optional feature from an unknown property without
assuming that every optional field belongs to every address. A producer can
keep one base schema URI instead of publishing one URI for each supported
combination of add-ins.