Version: 1.6.0 Date: April 2026 Status: Draft
License: This annex is part of the Open Okyline Language Specification and is subject to the same license terms (CC BY-SA 4.0). See the Core Specification for full license details.
This annex defines the internal reference mechanism
for composing and reusing schema fragments within a single document. It
introduces $defs for declaring reusable templates,
$ref for including them, and
$override/$remove for adapting included
structures. This enables DRY (Don’t Repeat Yourself) schema design while
maintaining full control over structural composition.
This annex defines the semantics of:
$defs)$ref)$override / $remove mechanismsIt specifies how Okyline schemas can be composed and extended using internal definitions, promoting reuse and consistency within a single document.
For versioned imports and external schema references, see Annex E — External Imports and Versioning.
Okyline supports schema references to promote reuse, modularity, and consistency within a schema document.
References allow a field or an object definition to reuse an existing
schema fragment identified by a logical name stored in
$defs.
Okyline distinguishes two complementary use cases:
field | $ref).In Okyline, $ref is an inclusion
mechanism: a referenced schema is treated as a base that can be
extended, explicitly overridden or partially removed. This differs
intentionally from JSON Schema, where $ref behaves as a
total substitution.
$defsOkyline reserves the special block $defs as a container
for reusable schema fragments. It is declared at the root
level, alongside $oky.
{
"$oky": {
"person": {
"$ref": "&Address",
"name|@ {2,50}": "Dupond"
}
},
"$defs": {
"Address": {
"street|@ {2,100}": "12 rue du Saule",
"city|@ {2,100}": "Lyon"
}
}
}$defs is declared at the root level,
not inside $oky.$defs is optional but, when present,
MUST contain a map of named schemas.$defs are not
interpreted as JSON properties of validated instances; they are reusable
definitions only.$defs MAY be targeted by
$ref using its name.$defs. Nested paths within $defs entries are
not addressable.$defs supports both object schemas and scalar type
definitions:
{
"$oky": {
"user": {
"email | $ref @": "&Email",
"score | $ref": "&Percentage"
}
},
"$defs": {
"Email|~$Email~ {5,100}": "user@example.com",
"Percentage|(0..100)": 50,
"Address": {
"street|@ {2,100}": "12 rue du Saule",
"city|@ {2,100}": "Lyon"
}
}
}Scalar definitions follow standard Okyline syntax: the key contains the name and constraints, the value is the example.
Within a document, definitions are referenced via:
&Name
Where Name is defined in $defs.
Examples:
&Address
&Email
&Person
Rules:
& denotes the current document’s definition
namespace.& refers to an entry in
$defs.Name does not exist in $defs, the
schema MUST be rejected.Note: For external references to definitions in other schemas, see Annex E.
field | $refA field can use a definition as its type via the
$ref constraint suffix:
{
"$oky": {
"person": {
"address | $ref": "&Address",
"name|@ {2,50}": "Dupond"
}
},
"$defs": {
"Address": {
"street|@ {2,100}": "12 rue du Saule",
"city|@ {2,100}": "Lyon"
}
}
}address) is the JSON property
name.$ref constraint indicates that the type and
value constraints of the field are taken from the target
definition.A property-level $ref MAY target:
If the value associated with field | $ref is a
single-element array containing a reference string, the
field is interpreted as a list whose elements use the referenced
schema:
{
"$oky": {
"company": {
"addresses | $ref": ["&Address"]
}
},
"$defs": {
"Address": {
"street|@ {2,100}": "12 rue du Saule",
"city|@ {2,100}": "Lyon"
}
}
}Semantics:
addresses is an array field.addresses must validate against
&Address.List size constraints can be combined:
"addresses | $ref [1,10]": ["&Address"]Okyline distinguishes two categories of constraints with different behaviors:
These constraints depend on the context of use and are NOT included from the referenced definition:
| Constraint | Description |
|---|---|
@ |
Required |
? |
Nullable |
[min,max] |
List size |
! |
Uniqueness in list |
% |
Default value |
| Label | Field description |
The same Address can be required in Order
but optional in UserProfile.
These constraints define the contract of the type
itself and ARE included from the referenced definition. They
can only be modified via $override:
| Constraint | Description |
|---|---|
# |
Key field(s) for object identity |
{min,max} |
String length |
(min..max) |
Numeric range |
('A','B') |
Enumeration |
~pattern~ |
Regex/format |
(%Compute) |
Computed validation |
Example:
{
"$oky": {
"user": {
"primaryEmail | $ref @": "&Email",
"backupEmail | $ref ?": "&Email"
}
},
"$defs": {
"Email|~$Email~ {5,100}": "user@example.com"
}
}~$Email~ and {5,100} → included from
Email@ vs ? → defined locally per usageExample values are included from the referenced
definition. They can be modified via $override if
needed.
An object schema can include another definition as a
template using a top-level $ref field:
{
"$oky": {
"Person": {
"$ref": "&Address",
"name|@ {2,50}": "Dupond"
}
},
"$defs": {
"Address": {
"street|@ {2,100}": "12 rue du Saule",
"city|@ {2,100}": "Lyon"
}
}
}Semantics:
$ref designates a template to
include.$ref (here name)
are treated as additional fields, provided there is no
name collision.The effective Person schema is conceptually equivalent
to:
{
"street|@ {2,100}": "12 rue du Saule",
"city|@ {2,100}": "Lyon",
"name|@ {2,50}": "Dupond"
}For object-level inclusion:
$ref MUST target an
object definition.For property-level $ref (section D.4), the target MAY be
scalar, object or array.
Object-level $ref is a structural
composition: it merges the fields of a template into the
current object. Each object-level $ref targets exactly one
template.
When a template contains conditional rules ($requiredIf,
$forbiddenIf, $appliedIf, etc.) or
$compute expressions, these stateful elements are injected
into the including object alongside the template’s fields. To preserve
the semantic integrity of these included elements, the following
restriction applies:
$remove not permitted: if a template
contains conditional rules or $compute expressions, the
including schema MUST NOT use $remove. Combining
$remove with such a template MUST cause a schema parsing
error.This restriction prevents broken references (removing a field that an included conditional rule depends on, which would produce an unsatisfiable schema).
Templates containing only fields (no conditional rules, no
$compute) are unaffected by this restriction.
When a schema includes a template via $ref:
$override
or $amend (see D.7). Otherwise, the schema MUST be
rejected.The value of $ref MUST be a single reference string
(e.g. "&Address"). An object-level $ref
targets exactly one template.
$removeOkyline provides the $remove directive to
exclude fields from included templates.
{
"$oky": {
"AnonymousPerson": {
"$ref": "&Person",
"$remove": ["email", "ssn"]
}
},
"$defs": {
"Person": {
"name|@ {1,50}": "John",
"age|@ (0..150)": 42,
"email|@ ~$Email~": "john@example.com",
"ssn|@": "123-45-6789"
}
}
}$ref injects all fields from the Person
template.$remove specifies fields to exclude
from the effective schema.Effective AnonymousPerson schema:
{
"name|@ {1,50}": "John",
"age|@ (0..150)": 42
}$remove MUST be an array of field names (strings).$remove MUST exist in the referenced
template. If a field does not exist, the schema MUST be rejected.$remove MUST NOT be used when $ref targets
a template containing conditional rules or $compute
expressions. See D.5.3 for the rationale.$override and $amendOkyline provides two directives to adapt a field inherited from a
template: $override replaces the field entirely,
$amend replaces only the constraint blocks specified by the
adapter. Both produce a new effective field by merging the base field
(from the referenced template) with the adapter.
Both directives appear as a suffix after the pipe in a field declaration, at the same position as standard field constraints:
"fieldName | $override <constraints>": <exampleValue>
"fieldName | $amend <constraints>": <exampleValue>
$override and $amend MUST NOT appear on the
same field declaration.
The following aspects of the base field MUST be preserved by both
$override and $amend:
obj, list,
map)$ref target (when the base field is itself a reference
to a definition)Any attempt to change one of these via $override or
$amend MUST cause a schema parsing error.
Let the base be the inherited field and the adapter
be the $override or $amend declaration. The
effective field is computed block-by-block:
$override: for each constraint block
(regex, string length, numeric range, enum, compute, label, example,
list bounds, presence flag, nullable flag, unique flag, key flag), the
effective value is the adapter’s value. Blocks not specified by the
adapter are removed from the effective field.$amend: for each constraint block, the
effective value is the adapter’s value if specified, otherwise the
base’s value. Blocks not specified by the adapter are
inherited from the base. For primitive presence flags
(@, ?, !, #),
$amend applies a logical OR: the flag is present in the
effective field if it is present in either the base or the adapter. As a
consequence, $amend cannot remove a flag from the base —
$override must be used for that.After the merge, the effective field carries exactly one block of each type, consistent with the single-constraint-per-type invariant defined in the Core Specification.
X | $override ... and X | $amend ... MUST
target a field X that exists at the point of application,
namely:
$ref at the same object
level, after applying $remove, or$appliedIf branch (see
Core §6.3.5). Otherwise, the schema MUST be rejected.{
"$oky": {
"Employee": {
"$ref": "&Person",
"name | $amend @": "John Doe",
"salary|@ (>=0)": 3000
}
},
"$defs": {
"Person": {
"name|? {1,50}": "John",
"age|@ (0..150)": 42
}
}
}Effective Employee schema (after merge of
name via $amend @):
{
"name|@? {1,50}": "John Doe",
"age|@ (0..150)": 42,
"salary|@ (>=0)": 3000
}The @ flag is added by $amend; the
? flag, the string length {1,50} and the
example are kept from the base. Using $override @ instead
would yield name|@, erasing {1,50},
? and the base example.
For an object that uses $ref, $remove,
$override, $amend and local fields, the
effective schema is computed conceptually in the following order:
$ref to its template and inject all fields.$remove
directives.$override and
$amend directives via the field merge defined in
D.7.3.| Situation | Error |
|---|---|
$ref value is not a single reference string |
Schema rejected |
$remove targets non-existent field |
Schema rejected |
$override or $amend targets non-existent
field (after removes) |
Schema rejected |
$override and $amend both specified on the
same field declaration |
Schema rejected |
$override or $amend attempts to change
field type, collection nature or $ref target |
Schema rejected |
Local field collides with included (no $override or
$amend) |
Schema rejected |
| Object-level cycle detected | Schema rejected |
$remove used with a $ref targeting a
template containing conditional rules or $compute |
Schema rejected |
$ref, $override and $remove
apply only to structural fields of an object.
A structural field is a field that is part of the object’s schema after:
$ref inclusion,$remove directives,$override directives,Fields that exist only inside $appliedIf (or
related conditional) branches are not
considered structural fields of the base object.
$override and $remove MAY only target
structural fields included from the template.$override or $remove targets a field
that does not exist structurally in the referenced definition, the
Okyline schema MUST be rejected.$requiredIf*,
$forbiddenIf*, $appliedIf*) declared in a
referenced definition are included by the schema that
uses $ref.$override or $remove (they apply to
structural fields, not the directives themselves).$remove restriction defined in D.5.3.During schema loading, conditional directives are interpreted after structural resolution is complete:
status,
paymentMethod) MUST exist as a structural
field of the object after
$ref/$remove/$override.$requiredIf* /
$forbiddenIf* MUST either:
$appliedIf branch).If a conditional directive references a field that does not exist according to these rules, the schema MUST be rejected.
| Feature | Description |
|---|---|
$defs |
Repository for reusable templates (root level, first level only) |
&Name |
Reference syntax (resolves in $defs) |
field \| $ref |
Reuse a template as the type of a field (scalar, object, or array) |
field \| $ref: ["&Name"] |
Array whose elements use the referenced template |
Object-level $ref |
Include all fields from a single template — structural composition (single inclusion only) |
$override |
Adapt an included field — replace its definition block-by-block, unspecified blocks are removed (see D.7) |
$amend |
Adapt an included field — replace only specified blocks, unspecified blocks are inherited from the base (see D.7) |
$remove |
Adapt by excluding an included field |
| Structural constraints | @, ?, [...], !,
%, labels — local, at usage |
| Value constraints | #, {...}, (...),
~...~, etc. — included, adapt via $override or
$amend |
Invariants preserved by
$override/$amend |
field type, collection nature, $ref target |
Conditional directives / $compute |
Included when $ref targets a template containing them;
$remove is not permitted in that case (see D.5.3); not
modifiable via $override/$remove |
| Object-level cycles | Forbidden (detected at load time) |
| Property-level recursion | Allowed |
Note: For external references and versioned imports, see Annex E — External Imports and Versioning.
{
"$okylineVersion": "1.4.0",
"$version": "1.0.0",
"$title": "Order Schema with Internal References",
"$oky": {
"order": {
"$ref": "&Auditable",
"orderId|@ # ~$OrderId~": "ORD-12345678",
"customerEmail | $ref @": "&Email",
"status|@ ($ORDER_STATUS)": "PENDING",
"items | $ref @ [1,100]": ["&OrderItem"],
"shippingAddress | $ref @": "&Address",
"billingAddress | $ref": "&Address",
"total|@ (%OrderTotal)": 100.50,
"$requiredIf status('SHIPPED','DELIVERED')": ["trackingNumber"],
"trackingNumber|{10,50}": "TRACK123456"
}
},
"$defs": {
"Email|~$Email~ {5,100}": "user@example.com",
"Auditable": {
"createdAt|@ ~$DateTime~": "2025-01-01T00:00:00Z",
"updatedAt|@ ~$DateTime~": "2025-01-01T00:00:00Z"
},
"Address": {
"street|@ {5,100}": "123 Main Street",
"city|@ {2,50}": "Paris",
"postalCode|@ {5,10}": "75001",
"country|@ {2}": "FR"
},
"OrderItem": {
"sku|@ # {5,20}": "SKU-12345",
"name|@ {2,200}": "Product Name",
"quantity|@ (1..1000)": 1,
"unitPrice|@ (>0)": 100.50
}
},
"$format": {
"OrderId": "^ORD-[0-9]{8}$"
},
"$compute": {
"ItemTotal": "unitPrice * quantity",
"OrderTotal": "total == sum(items, %ItemTotal)"
},
"$nomenclature": {
"ORDER_STATUS": "PENDING,CONFIRMED,SHIPPED,DELIVERED,CANCELLED",
"PAYMENT_METHOD": "CARD,PAYPAL,BANK_TRANSFER"
}
}$ref now targets exactly one template. Array values for
$ref, and the $keep directive, are no longer
part of the language. D.5.5, D.5.6, D.5.7 and D.8.2 of the 1.4.0 draft
are removed.$ref may now include a
template that contains conditional rules or $compute
expressions, provided no $remove directive is applied. Such
stateful elements are injected into the including object together with
the template’s fields.$remove
restriction for stateful templates.$ref.$amend alongside
$override with unified merge semantics: both preserve field
type, collection nature and $ref target.
$override erases base blocks not respecified,
$amend inherits them. Both directives apply uniformly in
$ref object-level inclusion and in $appliedIf
branches (see Core §6.3.5).End of Annex D — Internal Schema References (Normative)