| JSON Structure OAS Binding | August 2026 | |
| Vasters | Standards Track | [Page] |
This document defines a binding that allows an OpenAPI Specification (OAS)
[OAS] Schema Object to be expressed as a JSON Structure [JSTRUCT-CORE]
schema. It uses the schema-dialect selection mechanism OAS already provides:
the $schema keyword and the jsonSchemaDialect field, introduced in OAS
3.1. The binding is strictly additive and opt-in on a per-Schema-Object
basis. It introduces no new OpenAPI keywords, objects, or namespaces, and it
does not modify, fork, or republish the OpenAPI Specification.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://json-structure.github.io/oas-binding/draft-vasters-json-structure-oas-binding.html. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-vasters-json-structure-oas-binding/.¶
Source for this draft and an issue tracker can be found at https://github.com/json-structure/oas-binding.¶
Copyright (c) 2026 Microsoft Corporation. All rights reserved.¶
This is a pre-submission working draft, published for public review and comment. It is not a standard, it does not represent a commitment by Microsoft Corporation, and its content may change or be withdrawn at any time.¶
Permission is granted to read, reproduce, and redistribute this document in unmodified form, in whole or in part, for the purpose of review and comment, provided that this notice is retained. No other rights are granted, whether by implication, estoppel, or otherwise, and no licence to any patent, trademark, or other intellectual property right is granted by this document.¶
Microsoft Corporation intends to submit this document to a standards body. On submission, the contribution and intellectual property policies of that body govern this document and supersede this notice.¶
This document is provided "as is", without warranty of any kind.¶
The OpenAPI Specification [OAS] (OAS) describes the data of requests, responses, parameters, headers, and other message components with Schema Objects. By default a Schema Object is a JSON Schema (Draft 2020-12) document expressed in the OAS dialect.¶
OAS 3.1 introduced Schema Object dialect selection. Since that version, OAS
does not hard-wire the default. A Schema Object can declare a different
dialect through the $schema keyword. It can also inherit a
document-wide default through the OpenAPI Object's jsonSchemaDialect field.
This document uses that existing mechanism, and nothing else, to let a Schema
Object be a JSON Structure [JSTRUCT-CORE] schema.¶
The result is a strictly additive, opt-in binding:¶
Adoption is per Schema Object. A single Schema Object, an entire
components.schemas map, or a whole Description can use JSON Structure. The
rest of the Description is unaffected.¶
No new OpenAPI fields, objects, or namespaces are introduced. A JSON
Structure Schema Object appears wherever a Schema Object is allowed and is
referenced with an ordinary OpenAPI $ref.¶
JSON Structure's precise type system, inheritance model, discriminated
unions, cross-document reuse ($import), and unit annotations become
available to API designers and to code generators.¶
This document defines how conforming tooling recognizes such a Schema Object, how it is processed, and how its references resolve. It adds no fields to the OpenAPI Specification. OAS [OAS] remains authoritative for everything this document does not modify.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses the following terms:¶
An OpenAPI Schema Object as defined by the OpenAPI Specification's Schema Object section [OAS].¶
The URI, carried in $schema, that identifies the language and version a
schema resource is written in, as used by the OAS dialect-selection rules
("Specifying Schema Dialects") [OAS].¶
A Schema Object whose effective dialect (Section 4.3) is one of the canonical JSON Structure meta-schema URIs, or a separately configured derived URI whose base dialect is one of those URIs. Such a Schema Object is a JSON Structure schema and is processed per the JSON Structure specifications.¶
A self-contained JSON Structure schema identified by an $id, within which
$ref, $extends, and $import are resolved.¶
An optional JSON Structure keyword vocabulary (for example
JSONStructureValidation, JSONStructureUnits) that a schema activates
through $uses, subject to what its meta-schema offers.¶
An OpenAPI Description (OAD) [OAS]: the complete set of documents describing an API.¶
This document is a binding layered on OAS [OAS]. It applies to any OpenAPI
Description whose OAS version provides Schema Object dialect selection through
$schema and jsonSchemaDialect. OAS 3.1 introduced that mechanism, and
every subsequent OAS 3.x version retains it. This document is normative only
where it states requirements about JSON Structure Schema Objects.¶
The base-URI rules of Section 4.5 rely in part on OAS features
that postdate OAS 3.1. The OpenAPI Object's $self field is defined starting
with OAS 3.2 and has no equivalent in OAS 3.1. Tooling operating against an
OAS 3.1 Description MUST skip the $self step of
Section 4.5 and proceed to the next applicable base-URI
source. The absence of $self in an OAS 3.1 Description is expected and MUST NOT be treated as an error. In all other respects OAS applies unchanged and is
authoritative.¶
Where this document and OAS both describe the behavior of a Schema Object, this document governs only those Schema Objects whose dialect is a JSON Structure meta-schema (Section 5.2). A Schema Object that does not select a JSON Structure meta-schema is a plain OAS Schema Object and is outside the scope of this document.¶
Selecting a dialect other than the OAS default is an OAS-sanctioned
operation. OAS states that $schema on a schema resource root "MUST be used
to determine which dialect should be used when processing the schema." OAS
further states that this permits Schema Objects complying with other drafts of
JSON Schema than Draft 2020-12, and that tooling "MAY support additional
values of $schema" ([OAS], "Specifying Schema Dialects").¶
The JSON Schema drafts reachable that way are themselves mutually
incompatible. Successive drafts have redefined integer, changed whether
keywords adjacent to $ref apply, changed format from assertion to
annotation, and changed the meaning of items while leaving its syntax
intact. Each of those changes alters the validation outcome of an unmodified
document. A dialect is therefore already a distinct language selected by URI.
This document adds one more such language and introduces no mechanism that OAS
does not already define.¶
This document does not modify, fork, or republish the OpenAPI Specification.¶
This document is organized in two parts. Section 4 states requirements that apply to binding any schema dialect into an OpenAPI Description. It is written without reference to JSON Structure and is parameterized by a small set of declarations that a binding supplies. Section 5 supplies those declarations for JSON Structure and states the requirements specific to it.¶
OAS defines dialect selection. It does not define dialect processing, and the first part covers that layer. If a future OAS version specifies it, the first part of this document can be replaced by a reference to it without affecting the second.¶
This part applies to any schema dialect bound into an OpenAPI Description
through the $schema and jsonSchemaDialect mechanism, not only to JSON
Structure. A dialect binding is a specification that makes one or more
dialects usable in a Description by supplying the declarations of
Section 4.1 and satisfying the requirements of this part.¶
A dialect binding MUST declare the following. These declarations are the only dialect-specific inputs the rest of this part requires.¶
| Parameter | Meaning |
|---|---|
| Dialect URIs | The exact URIs that select the dialect, and for each, the vocabularies or add-ins active by default and those offered for opt-in. |
| Identity keyword | The keyword within a schema resource that carries its identity, used as its base URI and as the key for cross-document resolution. A binding MAY declare that its dialect has no identity keyword, in which case Section 4.5 does not apply and resources are addressable only by location. |
| Identity comparison | Whether identity comparison is byte-exact or subject to a declared normalization. |
| Cross-document mechanism | The keyword(s), if any, by which a resource incorporates definitions from another resource, and whether their values are absolute URIs. A binding MAY declare that its dialect has no such mechanism. |
| Type determination | The procedure by which tooling determines the data type of a value from the schema, for use with non-JSON serializations (Section 4.10). |
| Data media types | The media types whose message bodies are encoded according to the dialect, and for each, whether the encoding carries the schema needed to decode it (Section 4.11). A binding MAY declare that its dialect defines no encoding of its own. |
| Type naming and scope | What names a type in the dialect, and how a resource's scope for those names is determined (Section 4.9). A binding MAY declare that its dialect names no types, in which case Section 4.9 imposes no aggregation requirement. |
| Self-description | Which values an extracted resource requires beyond identity and dialect, including any value an inner declaration inherits from an enclosing one (Section 4.6). |
| Usable schema forms | Which of the dialect's schema forms are usable at a Schema Object position (Section 4.3), where the dialect admits forms that are not JSON objects. |
A binding MUST NOT redefine the OAS meaning of $schema, jsonSchemaDialect,
or the Reference Object, and MUST NOT introduce new fields into any OAS
Object.¶
Before dialect selection can apply, tooling MUST determine whether a JSON
object occupying a Schema Object position is an OAS Reference Object
([OAS], "Reference Object") or a Schema Object. The two are distinct
object types accepted at the same positions. Only a Schema Object is subject
to dialect selection (Section 4.3). A Reference Object is never
dialect-selected, whatever jsonSchemaDialect default is in scope.¶
A JSON object at a Schema Object position is a Reference Object if and only
if it is limited to the fixed fields of the OAS Reference Object ($ref,
summary, description) and carries no other properties. An object that
contains $ref together with any other property outside that fixed set is
not a valid Reference Object. Tooling MUST reject that object as invalid at
the OAS layer before dialect selection. It MUST NOT reinterpret the extra
properties as dialect Schema Object content. An object without $ref is
classified as a Schema Object and is then subject to dialect selection,
including when it carries $schema.¶
A components.schemas entry that is a bare Reference Object (for example
{ "$ref": "#/components/schemas/Pet" }) MUST NOT be treated as a schema in
the selected dialect when the document default jsonSchemaDialect names that
dialect. It remains an OAS Reference Object and is resolved on the OpenAPI
reference layer (Section 4.7). It has no schema content of its own,
and dialect selection therefore does not apply to it.¶
Tooling MUST classify a Schema Object position by this rule before performing dialect selection. Tooling MUST re-classify at every Schema Object position independently. Classification is not inherited from a containing resource.¶
A Schema Object belongs to a bound dialect when its effective dialect is one of that binding's declared dialect URIs (Section 4.1). The effective dialect is determined exactly as in the OAS dialect-selection rules (Section 4.24.7, "Specifying Schema Dialects", of [OAS]):¶
If the Schema Object is a schema-resource root and carries $schema, that
value is the dialect.¶
Otherwise, the OpenAPI Object's jsonSchemaDialect value, if present, is
the dialect.¶
Otherwise, the OAS dialect applies and no binding governs the Schema Object.¶
A dialect whose schemas natively carry $schema yields self-describing
Schema Objects. Pasting such a schema into a Schema Object position, with its
$schema intact, is sufficient to select the dialect.¶
A Schema Object position accepts a JSON object, and in the OAS dialect also a boolean ([OAS]). It accepts neither a JSON array nor a JSON string. A dialect whose schema forms include such values is bound only for the forms that position accepts. A binding MUST declare which of its dialect's forms are usable there (Section 4.1). Tooling MUST reject a Description that places any other form of a bound dialect at a Schema Object position. The restriction applies to that position alone. Inside a Schema Object, the dialect's remaining forms are unrestricted.¶
A schema-resource root is a Schema Object that is independently addressable
by the OpenAPI Description or that tooling extracts as an independent schema
document. This includes every entry in components.schemas. It also includes
an inline Schema Object at a request body, response, parameter, callback,
webhook, or other OAS Schema Object position, once tooling extracts it for
standalone processing.¶
Schema Objects nested inside an already-selected resource are part of that
resource. They do not establish a second dialect selection context. A nested
$schema value MUST NOT override the resource-root dialect. Tooling MUST
reject a nested $schema that would make the nested object ambiguous.¶
Dialect selection is per schema-resource root, not global to the Description. An OpenAPI Description MAY contain ordinary OAS Schema Objects and Schema Objects in one or more bound dialects. Tooling MUST apply the effective dialect independently at each resource root.¶
Tooling that supports a binding MUST recognize the dialect URIs that binding declares and MUST process a Schema Object bearing one of them as a schema in that dialect.¶
Matching a $schema or jsonSchemaDialect value against a declared
dialect URI MUST be exact, byte-for-byte string comparison unless the
binding declares a normalization. Tooling MUST NOT apply URI normalization,
trailing-slash equivalence, or version-range matching on its own
initiative. A future version of a dialect that defines new URIs is a new or
updated binding.¶
Tooling that does not recognize a dialect URI MUST treat the Schema Object as an unknown dialect. It MUST NOT process the Schema Object as an OAS-dialect (JSON Schema Draft 2020-12) schema. Unrelated dialects routinely assign different meaning to the same keyword spellings. A reader MUST preserve the raw Schema Object and its surrounding Description for pass-through or explicit diagnostic handling, and MUST NOT silently deserialize it as an OAS Schema Object. A tool MAY fail the operation when it cannot preserve or expose the unknown object, but MUST report the unknown dialect as the cause.¶
Where a dialect permits deriving a custom meta-schema from a declared one, the derived URI does not satisfy exact-match recognition. Tooling MAY be separately configured to recognize specific derived URIs, and once so configured MUST process Schema Objects bearing them under the declared dialect they extend. Tooling that is not so configured MUST apply the unknown-dialect rule above.¶
A resource-root Schema Object SHOULD carry $schema explicitly, even when
the document default already selects the same dialect, so that the schema
remains self-describing when extracted from the Description.¶
Tooling MUST construct and materialize defaults per Section 4.5 and Section 4.6 whenever a Schema Object lacking them is extracted for standalone processing.¶
This section applies when the binding declares an identity keyword (Section 4.1). "Identity keyword" below means that keyword.¶
A resource-root Schema Object SHOULD carry its own identity keyword. When it does not, tooling still needs a stable, unique identifier for it, for example to run the Schema Object through a standalone validator for its dialect. This section defines how tooling MUST construct that default. The construction combines the Description's base URI with the Schema Object's location inside the Description.¶
A default identity constructed this way is for standalone processing only. It is NOT registered as a target for the dialect's cross-document mechanism. Section 4.8 matches exclusively against explicitly declared identities. A Schema Object intended to be reachable by another entry in the Description MUST carry an explicit identity.¶
Find the Description's base URI. Tooling MUST try the following sources in order, using the first one that applies. This is the same precedence OAS itself uses for base URI determination (Appendix F, "Examples of Base URI Determination and Reference Resolution", of [OAS]):¶
the OpenAPI Object's $self field, if present (OAS Appendix F.1);¶
otherwise, the base URI of an encapsulating entity, if any, such as a
multipart/related archive (OAS Appendix F.2);¶
otherwise, the Description's retrieval URI: the URI it was actually fetched from (OAS Appendix F.3);¶
otherwise, an application-specific default base URI, if the tooling defines one (OAS Appendix F.4).¶
None of these applies to a Description that was authored offline and never
assigned a location or a $self value. In that case tooling MUST require an
explicit identity on the Schema Object and MUST NOT proceed with extraction,
cross-document resolution, or standalone validation until one is supplied.¶
Find the Schema Object's location. Tooling MUST determine the JSON
Pointer [RFC6901] from the Description's document root to the Schema
Object, for example /components/schemas/TelemetryMessage.¶
Combine the two. The default identity is the base URI with that JSON Pointer appended as an [RFC3986] fragment, constructed deterministically as follows, in order:¶
Take each JSON Pointer reference-token (for example components,
schemas, TelemetryMessage) as a Unicode string and encode it as
[RFC3629] UTF-8 octets.¶
Apply [RFC6901] reference-token escaping to the token before
percent-encoding: a literal ~ becomes ~0 and a literal / becomes
~1. Join the escaped tokens with / to form the JSON Pointer string.¶
Percent-encode the resulting pointer string for use as a URI fragment.
Every octet not permitted literally in an [RFC3986] fragment
production MUST be percent-encoded. This includes any literal %
character, which becomes %25, so that the result cannot be misread as a
percent-encoded triple. Percent-encoding MUST use uppercase hexadecimal
digits, for example %2F and not %2f.¶
The base URI MUST be resolved according to the OAS base-URI rules before the fragment is appended. Any existing fragment on the base URI MUST be removed first.¶
The result has the form base-URI + # + percent-encoded-JSON-Pointer.¶
This construction MUST be deterministic and idempotent. Re-deriving the default identity for the same Schema Object location in the same Description MUST always produce the same byte sequence. Identity comparison is byte-exact unless the binding declares otherwise (Section 4.1).¶
For example, if the Description's base URI is
https://example.com/api/openapi.yaml and the Schema Object is at
/components/schemas/TelemetryMessage, the default identity is
https://example.com/api/openapi.yaml#/components/schemas/TelemetryMessage.
An explicit identity on the Schema Object always takes precedence over this
default.¶
A Schema Object outside components.schemas exercises the escaping rules.
Consider an inline Schema Object at the JSON request body of
POST /orders/{orderId}, in a Description with the same base URI. Two of its
reference-tokens contain characters that the two escaping steps treat
differently:¶
| Reference-token | After step 2 (JSON Pointer escaping) | After step 3 (fragment encoding) |
|---|---|---|
paths
|
paths
|
paths
|
/orders/{orderId}
|
~1orders~1{orderId}
|
~1orders~1%7BorderId%7D
|
post
|
post
|
post
|
requestBody
|
requestBody
|
requestBody
|
content
|
content
|
content
|
application/json
|
application~1json
|
application~1json
|
schema
|
schema
|
schema
|
A / inside a path template or a media type is part of the reference-token,
not a pointer separator. Step 2 therefore escapes it as ~1. That ~1 then
passes through step 3 unchanged, since ~ and digits are permitted literally
in an [RFC3986] fragment. The braces of the path template are not
permitted there, so step 3 percent-encodes them with uppercase hexadecimal
digits. The resulting default identity is:¶
https://example.com/api/openapi.yaml#/paths/~1orders~1%7BorderId%7D/post/requestBody/content/application~1json/schema¶
Tooling APIs that process an encapsulated or offline Description MUST expose
the selected base URI as an explicit input when it is not available from
$self or a retrieval URI. A filesystem path MUST NOT be concatenated
directly into the default identity. It MUST first be converted to the URI
used by the tool's retrieval and base-URI model.¶
A Schema Object embedded in an OpenAPI Description commonly omits $schema
and its identity keyword. $schema is supplied by jsonSchemaDialect
(Section 4.3) and the identity by Section 4.5. Both
are conveniences that depend on context. A standalone validator for the
dialect has no notion of jsonSchemaDialect and no notion of the Schema
Object's position within a larger document.¶
Tooling that extracts a Schema Object from its containing Description in order to validate it, incorporate it elsewhere, or otherwise process it outside OAS context MUST first materialize both defaults as literal properties on the extracted document:¶
$schema, set to the effective dialect determined per
Section 4.3;¶
the identity keyword, set to the Schema Object's own value if present, or otherwise to the value constructed per Section 4.5.¶
The extracted document MUST be self-describing and independently valid in its
dialect without reference to the originating Description. Tooling MUST NOT
hand an extracted Schema Object to a standalone validator with $schema or
the identity keyword left absent on the assumption that OAS-level context
will supply them.¶
A binding MAY declare further values that the extracted document would
otherwise take from context (Section 4.1). That context is not
always the OpenAPI Description. Where a dialect's own nesting rules let an
inner declaration inherit a value from an enclosing one, extraction destroys
that value just as thoroughly as extraction from a Description destroys
jsonSchemaDialect. Tooling performing an extraction MUST materialize every
such value, whichever context supplied it. The materialized value MUST be the
one in effect at the declaration's position in the originating document.¶
Any materialized value MUST be deterministic for the same Description and JSON Pointer. Where a materialized value could collide with a declaration already present in the extracted document, tooling MUST reject the extraction. Tooling MUST NOT rename either declaration.¶
A Schema Object in a bound dialect is a self-contained schema resource, once classified as a Schema Object rather than a Reference Object (Section 4.2). Two reference layers coexist and do not mix:¶
The OpenAPI reference layer: an outer OpenAPI $ref that targets a Schema
Object as a whole, for example #/components/schemas/TelemetryMessage.
This resolves per OAS [OAS], unchanged, at any position in the
Description. That includes positions inside a Schema Object that has not
itself adopted a bound dialect, such as an array wrapper's
items: { "$ref": "#/components/schemas/Pet" }.¶
The dialect's own reference layer: whatever referencing keywords the dialect defines, used within a Schema Object that has adopted that dialect. These resolve as the dialect specifies. That resolution need not resemble OAS reference resolution and need not be able to reach OAS component addresses at all.¶
Dialect selection is per schema-resource-root (Section 4.3). These
layers are therefore scoped, not global. The dialect's rules apply only once
a reference node is already inside that resource's own body. An ordinary OAS
$ref pointing at a resource from outside it stays on the OpenAPI
reference layer and is untouched.¶
Tooling MUST determine the reference layer before resolving a reference. At
an OAS Schema Object position, a sibling $ref is an OAS reference and is
resolved against the containing Description. After a resource root has
adopted a bound dialect, a reference encountered inside its body is valid
only where that dialect permits it. A reader MUST NOT recursively reinterpret
an OAS reference target as dialect content until that target Schema Object
has independently undergone dialect selection.¶
This section applies when the binding declares a cross-document mechanism (Section 4.1).¶
A Description registers a set of schema resources. The set comprises every Schema Object in a bound dialect that carries an explicit identity (Section 4.5). Tooling MUST attempt to resolve a cross-document reference in the following order, using the first source that satisfies it:¶
a registered identity within the Description;¶
a caller-supplied registry or cache mapping the reference URI to a previously retrieved schema resource, without performing network access;¶
network retrieval of the URI, over a secure transport such as HTTPS.¶
Network retrieval is OPTIONAL for a conforming implementation and MUST be disabled by default. Tooling MUST NOT attempt network retrieval unless it has been explicitly configured to allow it. When retrieval is disabled, not supported, or not enabled for the target host, tooling MUST fail with a diagnostic identifying the unresolved reference. Tooling MUST NOT silently skip the reference or substitute an empty definition.¶
When network retrieval is enabled, tooling MUST provide controls for allowed schemes and hosts, maximum document size, retrieval timeouts, redirect policy, caching, and cycle detection. Tooling MUST prevent cross-document resolution from becoming an SSRF or unrestricted local-file access primitive.¶
Identity comparison uses two related but distinct notions. Both are derived from an explicitly declared identity:¶
Resource identity is the identity value exactly as declared, including any fragment it carries. It is the value used as the base URI for references resolved within that resource.¶
Lookup key is the fragment-free form of a resource identity: the same absolute URI with any fragment removed. It is used only for matching a cross-document reference against registered resources.¶
Matching compares lookup keys. Tooling MUST strip any fragment from the reference value and from each registered identity, then compare the two byte-for-byte.¶
This comparison discards fragments. Two registered identities that share a fragment-free prefix therefore reduce to the same lookup key, even though they remain distinct resource identities. Tooling MUST reject a Description in which more than one registered identity reduces to the same lookup key. Tooling MUST reject a Description that registers the same resource identity more than once.¶
A schema resource retrieved from outside the Description need not itself be an OpenAPI Description or a JSON Schema document. OAS requires every document in an OpenAPI Description to have an OpenAPI Object or a Schema Object at its root ([OAS], "OpenAPI Description Structure"), and treats other roots as implementation-defined. Such a retrieved resource is therefore not part of the OAD. It is an input to dialect processing, obtained and interpreted under this section and the dialect's own rules, and OAS document-structure requirements do not apply to it. Tooling MUST NOT attempt to parse it as an OpenAPI document, and MUST NOT expose its contents at OAS component addresses.¶
A Description can hold many schema resources. These are every
components.schemas entry, and every inline Schema Object at a request body,
response, parameter, or header position. Each is a separate resource with its
own identity (Section 4.5) and its own scope for whatever
names its dialect gives to types.¶
Type identity is outside the scope of OAS, which places "class hierarchies" in the application form of the data, "beyond the scope of this specification" ([OAS], "Parsing and Serializing"). A binding supplies it, as it supplies the other parameters of Section 4.1.¶
Scope becomes observable once a consumer treats the Description as a single type space. A code generator does this when it emits one module for the Description. Two resources can carry the same type name without either being wrong, where the binding's rules place them in different scopes. A consumer that flattens them produces a collision that exists nowhere in the Description.¶
A dialect binding MUST declare what names a type in its dialect and how a resource's scope for those names is determined (Section 4.1). A dialect MAY derive scope from the schema resource, and it MAY take scope from a declaration the author writes inside the schema. The binding states which.¶
Tooling that aggregates the Schema Objects of a Description into one type space MUST preserve the scopes the binding's rules assign. It MUST NOT introduce a name collision that those rules do not already produce. It MUST NOT resolve a collision by renaming a declaration or by synthesizing a scope. Where the binding's rules place two differing declarations in one scope, tooling MUST report a diagnostic and MUST NOT choose between them.¶
Some media types are not self-describing as to type. These include
application/x-www-form-urlencoded, multipart media types, and text/plain
values in Parameter, Header, and Encoding Objects. For those, OAS requires
implementations to inspect the schema to determine each value's type before
parsing or serializing it ([OAS], "Parsing and Serializing"). OAS states
that procedure in terms of the OAS dialect's own keywords, following $ref
and allOf. That procedure does not apply to a Schema Object in another
dialect, whose keywords need not exist or need not mean the same thing.¶
A binding MUST declare an equivalent type-determination procedure (Section 4.1) that, given a starting-point Schema Object in its dialect, yields for each named or positioned value either a determined JSON data type or an explicit "undetermined" result. The procedure MUST be deterministic and MUST NOT depend on instance data.¶
Tooling MUST use the binding's procedure, and MUST NOT fall back to the OAS procedure, whenever a Schema Object in a bound dialect appears in:¶
a Media Type Object's schema or itemSchema for a non-JSON media type;¶
a Parameter or Header Object using schema rather than content;¶
a value correlated with an Encoding Object through encoding,
prefixEncoding, or itemEncoding.¶
Where the procedure yields "undetermined", tooling MUST report a diagnostic identifying the value and MUST NOT guess a type. Where a binding declares no such procedure, its dialect MUST NOT be used at the positions listed above, and tooling MUST reject a Description that does so.¶
The itemSchema field applies its Schema Object to each item of a sequential
media type independently ([OAS]). A Schema Object in a bound dialect is
valid at an itemSchema position on the same terms as at a schema
position. It is a schema-resource root (Section 4.3), and it
describes one item, not the sequence.¶
A dialect may define a wire encoding for the data it describes, and not only a vocabulary for describing that data. Where it does, a message body in that encoding is decoded from the schema by the dialect's own rules.¶
A binding MUST declare the media types whose bodies its dialect encodes (Section 4.1). A binding MAY declare none, in which case bodies are encoded per their media type independently of the dialect, and this section does not apply.¶
Section 4.10 does not apply to a body of a declared data media type. That procedure exists so that OAS can construct and parse form, multipart, and text values, which carry no type information of their own. A body the dialect encodes carries whatever the dialect's encoding provides, and the dialect decodes it as a whole. Tooling MUST NOT apply the type-determination procedure to such a body.¶
For each declared media type, a binding MUST state whether the encoding carries the schema needed to decode the body:¶
Where the encoding carries that schema, tooling MUST decode the body using the schema the body carries. Tooling MUST NOT decode the body using the Schema Object in place of the schema on the wire. The Schema Object states what the Description expects, and tooling MUST report a diagnostic where the two disagree in a way the dialect does not reconcile.¶
Where the encoding does not carry that schema, the Schema Object is the schema the body was encoded against. Tooling MUST NOT decode a body whose encoding schema it cannot establish, and MUST NOT substitute a different schema on the assumption that the two are compatible.¶
A Schema Object whose dialect encodes the body is part of the wire contract. Editing it changes what is on the wire. Where the dialect defines its own rule for reading data written against a different version of a schema, the binding MUST state how that rule relates to a change made in the Description.¶
A schema carried in a body is untrusted input. Tooling MUST validate it before use and MUST apply the limits of Section 8 to it.¶
The OpenAPI Initiative publishes two JSON Schemas per OAS minor version for
validating OpenAPI documents. The schema iteration deliberately does not
validate Schema Objects, since it makes no assumption about the dialect in
use. The schema-base iteration does validate them, and constrains
jsonSchemaDialect and $schema to the OAS dialect.¶
A Description that uses any bound dialect is therefore validatable against
the schema iteration and is NOT validatable against the schema-base
iteration. A schema-base failure caused solely by a bound dialect URI is
expected and is not a defect in the Description. Tooling that validates
Descriptions MUST NOT treat schema-base as the authoritative structural
check for a Description containing bound-dialect Schema Objects. Such tooling
SHOULD validate against the schema iteration together with the dialect's own
meta-schema applied to each Schema Object.¶
This part is a dialect binding, in the sense of Section 4, for JSON Structure [JSTRUCT-CORE]. It supplies the declarations of Section 4.1 and states the requirements specific to JSON Structure.¶
A JSON Structure Schema Object is a Schema Object whose effective dialect (Section 4.3) is one of the canonical URIs in Section 5.2, or a separately configured derived URI whose base dialect is one of those canonical URIs.¶
| Parameter | Value for JSON Structure |
|---|---|
| Dialect URIs | The three URIs in Section 5.2, with the active and offered add-ins given there. |
| Identity keyword |
$id [JSTRUCT-CORE]. |
| Identity comparison | Byte-exact; no normalization. |
| Cross-document mechanism |
$import and $importdefs [JSTRUCT-IMPORT], whose values MUST be absolute URIs (Section 5.6). |
| Type determination | Section 5.11. |
| Data media types | None. JSON Structure defines no framing of its own. It does fix the JSON representation of several types (Section 5.10). |
| Type naming and scope | A type is named by name. Scope is derived from the schema resource: a resource's scope is its root namespace (Section 5.7). |
| Self-description |
name is additionally required on every resource root (Section 5.3). No JSON Structure declaration inherits a value from an enclosing declaration. |
| Usable schema forms | Every JSON Structure schema is a JSON object. All forms are usable. |
The following URIs identify the JSON Structure dialects:¶
| Meta-schema URI | Meaning |
|---|---|
https://json-structure.org/meta/core/v0/#
|
Core types and keywords only. |
https://json-structure.org/meta/extended/v0/#
|
Core plus the JSONStructureImport add-in active by default, and offering (for opt-in through the schema's own $uses) the JSONStructureAlternateNames, JSONStructureUnits, JSONStructureValidation, and JSONStructureConditionalComposition add-ins. |
https://json-structure.org/meta/validation/v0/#
|
Core plus JSONStructureImport and all four extended add-ins (JSONStructureAlternateNames, JSONStructureUnits, JSONStructureValidation, JSONStructureConditionalComposition), all active by default; $uses is not required to enable any of them. |
The extended meta-schema is RECOMMENDED as the document default for API
design. It makes $import available while keeping validation and
composition opt-in.¶
JSON Structure Core permits a custom meta-schema to extend one of these by
giving the derived meta-schema its own $id and using $import to pull in
the foundational meta-schema's definitions ([JSTRUCT-CORE], "Meta-Schemas").
Such a URI is subject to the derived-URI rule of Section 4.4.
Configuring tooling to recognize one typically means resolving the
meta-schema document once, offline, and confirming that it imports one of the
three canonical meta-schemas. A custom meta-schema MUST document which
add-ins it activates by default and which it only offers.¶
name
Beyond the $schema and $id materialization required by
Section 4.6, every JSON Structure schema resource root MUST
carry a name [JSTRUCT-CORE]. This requirement is unconditional. It
applies to a root that declares type at the top level, to a root that uses
$root to designate a type defined under its own definitions, and to a
root that only holds definitions and is never itself instantiated.¶
Within an OpenAPI Description the requirement applies to every
components.schemas entry. It also applies to an anonymous inline Schema
Object at a request body, response, or parameter position, once that object is
extracted for standalone validation. Tooling performing such an extraction
MUST materialize a name when the Schema Object does not supply one, for
example by deriving it from the Schema Object's JSON Pointer location
(Section 4.5).¶
The materialized name MUST be a valid JSON Structure identifier and MUST be
stable for the same Description and JSON Pointer. If the derived name
collides with a declared name in the extracted document, tooling MUST reject
the extraction and MUST NOT rename either declaration. The OpenAPI component
key remains the OAS address. It does not implicitly replace an explicit JSON
Structure name. A further uniqueness requirement applies across the schema
resources of one Description when they are aggregated
(Section 5.7).¶
A JSON Pointer cannot be used as a name unchanged. JSON Structure restricts
identifiers to [A-Za-z_][A-Za-z0-9_]* ([JSTRUCT-CORE]), which excludes
the / and ~ a pointer is built from. Reproducing the whole pointer is
also unnecessary. $id already records the exact location, so name only
has to be a stable, readable identifier that is unique within the extracted
document. Tooling SHOULD derive one in this order:¶
If the Schema Object is a components.schemas entry whose component key
is already a valid JSON Structure identifier, use the component key. OAS
constrains component keys to ^[a-zA-Z0-9\.\-_]+$ ([OAS]), so most
keys in existing Descriptions qualify unchanged.¶
Otherwise, derive the name from the identifying parts of the position. For a request body or response, those are the path template, the HTTP method, and the role. Do not use every reference-token of the pointer.¶
If neither produces a valid identifier, reject the extraction and require
an explicit name.¶
Consider a Description that selects the JSON Structure Core dialect for the whole document and carries an anonymous inline Schema Object at a request body position:¶
openapi: 3.2.0
$self: https://example.com/api/openapi.yaml
jsonSchemaDialect: https://json-structure.org/meta/core/v0/#
paths:
/orders:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
orderId:
type: uuid
total:
type: decimal
¶
The Schema Object declares no $schema, no $id, and no name. None of the
three is needed while it is read in place. The dialect comes from
jsonSchemaDialect, and the position within the Description supplies the
address. Extraction for standalone validation removes both, so all three MUST
be materialized:¶
$schema: https://json-structure.org/meta/core/v0/#
$id: https://example.com/api/openapi.yaml#/paths/~1orders/post/requestBody/content/application~1json/schema
name: OrdersPostRequestBody
type: object
properties:
orderId:
type: uuid
total:
type: decimal
¶
$schema is the effective dialect inherited from jsonSchemaDialect, and
$id is the default identity constructed per Section 4.5.¶
name comes from step 2. Three parts of the position identify it, and each
contributes one segment:¶
| Part | Value at this position | Segment |
|---|---|---|
| Path template |
/orders
|
Orders
|
| Method |
post
|
Post
|
| Role |
requestBody
|
RequestBody
|
Each segment drops the characters outside [A-Za-z0-9_] and capitalizes its
first letter. The segments are then concatenated in that order, giving
OrdersPostRequestBody. The remaining reference-tokens of the pointer,
paths, content, application/json, and schema, contribute nothing.
They are structural, not identifying.¶
Distinct positions can still derive the same name. Two media types under one
operation, application/json and application/xml at POST /orders, occupy
different pointers but share path, method, and role. So do the path templates
/orders and /Orders, which differ in the Description but not after the
leading character is capitalized. Tooling MAY append further distinguishing
segments, such as the media type, provided the result stays deterministic.
Where it cannot, it MUST reject the extraction and MUST NOT emit the same
name twice. The author resolves the conflict by declaring an explicit name
on one of the Schema Objects.¶
A components.schemas entry usually needs none of this. An entry stored
under TelemetryMessage derives TelemetryMessage by step 1. The component
key remains the OAS address and not the JSON Structure name. An explicit
name on the Schema Object therefore still takes precedence, and the two
need not be equal.¶
Within a JSON Structure Schema Object, keywords are interpreted per JSON
Structure [JSTRUCT-CORE], which defines the full type system. That type
system covers primitive and precise numeric types, the
object/array/set/map/tuple/choice compound types and their
member, item, and value declarations, nullability via type union, and
inheritance via abstract and $extends. These JSON Structure constructs
replace the correspondingly named OAS and JSON Schema constructs, which do
not apply in this dialect.¶
Inheritance is expressed with abstract and $extends ([JSTRUCT-CORE]).
The OAS discriminator object combined with allOf is not an inheritance
mechanism in a JSON Structure Schema Object, and MUST NOT be used to express
type extension.¶
Discriminated unions use the JSON Structure choice type, in either its
tagged-union or inline-union form, with selector naming the discriminant
property ([JSTRUCT-CORE]).¶
A JSON Structure Schema Object MUST NOT include the OAS discriminator
field. Its presence makes the Schema Object non-conforming with this binding.
Tooling MUST NOT interpret an OAS discriminator field as a discriminator
when present. Tooling MUST use selector, which is JSON Structure's own
discriminator mechanism.¶
Where present, the allOf, anyOf, oneOf, not, and if/then/else
keywords of the JSONStructureConditionalComposition add-in [JSTRUCT-COMPOSITION]
are boolean validation combinators only. They do not merge or intersect type
definitions and MUST NOT be interpreted as structural composition. Structural
reuse is expressed only with $extends (Section 5.4.1) and $import
(Section 5.6).¶
Appendix A tabulates the OAS-dialect constructs that change meaning in this dialect, for readers migrating an existing Schema Object.¶
The two reference layers of Section 4.7 are instantiated for JSON
Structure as follows. The OpenAPI layer is unchanged. The dialect layer is
$ref, $extends, and $import used within a Schema Object that has
adopted a JSON Structure dialect. $ref and $extends address named types
under the schema's own #/definitions/.... Cross-document reuse uses the
JSONStructureImport add-in [JSTRUCT-IMPORT] instead
(Section 5.6). An inner $ref MUST NOT be used to reach a
components.schemas entry.¶
After a resource root has adopted a JSON Structure dialect, a $ref
encountered inside its body is valid only where JSON Structure Core permits
it: as the value of type, or through the corresponding $extends and
import forms.¶
An OAS or JSON Schema $ref is a general-purpose node-reference mechanism. It
is a sibling property that can stand in for an entire Schema Object at any
position, as in { "$ref": "#/components/schemas/Foo" }. JSON Structure has
no equivalent. Its $ref exists only to name the type referenced by a type
attribute. $ref MUST therefore appear only as the value of a type
attribute, as in { "type": { "$ref": "#/definitions/Foo" } }
([JSTRUCT-CORE]). It MUST NOT be used as a sibling property standing in for
a whole schema.¶
$ref Names a Type, Not a Node
This is easy to lose sight of when moving a properties, items, or
choices member from a plain OAS or JSON Schema Schema Object into a JSON
Structure Schema Object. The OAS reference syntax parses without complaint at
the same position. Assume the array schema below has itself adopted the JSON
Structure dialect:¶
{
"type": "array",
"items": { "$ref": "#/components/schemas/Pet" }
}
¶
This is NOT valid JSON Structure. $ref here is a sibling of items, not
the value of a type attribute. It also targets a components.schemas
entry, which JSON Structure's own $ref cannot reach in any case
(Section 5.6), since $ref and $extends are document-local and
address only the schema's own #/definitions. A generic JSON or YAML parser
accepts the form above, so the violation surfaces only when the Schema Object
is validated with a JSON Structure-aware tool. The principled form names a
type for items to hold and points that type reference at a locally defined
type:¶
{
"type": "array",
"items": { "type": { "$ref": "#/definitions/Pet" } },
"definitions": {
"Pet": {
"type": "object",
"name": "Pet",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
}
¶
If Pet is also used verbatim elsewhere in the Description as a whole Schema
Object, an ordinary OAS $ref to #/components/schemas/Pet is used at that
position instead of duplicating it. Only positions nested inside another
Schema Object's properties, items, or choices require the local
definitions copy. Those positions cannot carry an OAS $ref of
their own. Section 5.7 governs how such a copy relates to a
like-named declaration in another schema resource.¶
$import and $importdefs
$ref and $extends are document-local. A JSON Structure Schema Object
cannot reach a type defined in another components.schemas entry through
them. Reuse across entries, and across documents, uses the
JSONStructureImport add-in [JSTRUCT-IMPORT]. That add-in is active by
default under the extended and validation meta-schemas. It defines the
$import and $importdefs keywords, their copy-not-link semantics,
definition shadowing, import error handling, and cycle rejection.¶
$import and $importdefs are this binding's cross-document mechanism
(Section 4.1), and their values MUST be absolute URIs per
[JSTRUCT-IMPORT]. This binding does not relax that requirement and does not
introduce relative import URIs for use inside an OpenAPI Description. A JSON
Structure Schema Object with a relative $import or $importdefs value is
non-conforming. Tooling MUST reject it and MUST NOT resolve it against the
importing resource's base URI.¶
Resolution follows Section 4.8 unchanged: an in-Description
$id match first, then a caller-supplied registry or cache, then optional
and explicitly enabled network retrieval. Resource identity is the declared
$id. The lookup key is that value with any fragment removed. A JSON
Structure Schema Object that is to be importable therefore MUST declare an
explicit $id (Section 4.5). A default-constructed $id is
never used for import matching.¶
Once a resource is obtained, tooling validates that it is a JSON Structure
schema and copies its definitions into the designated namespace. It does so
identically whether the resource came from the Description, a cache, or the
network. A resource obtained from outside the Description is not part of the
OAD (Section 4.8). A single canonical type library can
therefore be shared across many APIs without itself being an OpenAPI
document. Import errors, deduplication, and cycle rejection follow
[JSTRUCT-IMPORT] unchanged. An importing entry MUST additionally activate,
through its meta-schema or its own $uses, any add-ins used by the
definitions it imports.¶
This section supplies the type naming and scope declaration required by
Section 4.9. A JSON Structure type is named by name, and a schema
resource's scope is its root namespace ([JSTRUCT-CORE]).¶
Two entries of one Description can therefore each declare a type named Pet
without either being wrong. Section 5.5.1 makes that a
routine outcome. A type needed at a nested properties, items, or
choices position has to be copied into the containing resource's own
definitions, since that position cannot carry an OAS $ref.¶
The aggregate required by Section 4.9 is a JSON Structure document whose
root definitions carries one namespace per schema resource, named by that
resource's name (Section 5.3). The resource's
root type and everything under its own definitions, nested namespaces
included, are placed in that namespace unchanged except for pointer
rewriting. A document-local $ref, $extends, or $root that addressed
#/definitions/X within the resource MUST be rewritten to address X under
the resource's namespace. Definitions that $import or $importdefs copied
into a resource (Section 5.6) are already part of that resource's
namespace hierarchy and move with it.¶
The namespaces are keyed on name. A name MUST therefore be unique across
the schema resources of one Description for that Description to be
aggregatable. OAS component keys are unique by construction, and step 1 of
Section 5.3 carries that uniqueness into the
derived names. A duplicate can only come from an explicit name that repeats
another resource's. Tooling MUST reject such a Description when aggregating.¶
The aggregate is a type library, not an instance schema. It declares no
type and no $root of its own. It does not replace the individual
resources, each of which remains addressable by its own $id
(Section 4.5).¶
Consider a Description with a Pet entry and a PetListResponse entry that
needs a pet type at a nested items position:¶
components:
schemas:
Pet:
name: Pet
type: object
properties:
id: { type: uuid }
name: { type: string }
required: [id, name]
PetListResponse:
name: PetListResponse
type: object
properties:
pets:
type: array
items:
type: { $ref: "#/definitions/Pet" }
required: [pets]
definitions:
Pet:
name: Pet
type: object
properties:
id: { type: uuid }
name: { type: string }
tags:
type: set
items: { type: string }
required: [id, name]
¶
The two Pet declarations are different types. The nested one carries tags
and the component entry does not. Each resource validates on its own.
Aggregated, they occupy separate namespaces, and the inner $ref is
rewritten to match:¶
$schema: https://json-structure.org/meta/core/v0/#
$id: https://api.example.com/openapi.yaml#types
name: PetstoreTypes
definitions:
Pet:
Pet:
name: Pet
type: object
properties:
id: { type: uuid }
name: { type: string }
required: [id, name]
PetListResponse:
PetListResponse:
name: PetListResponse
type: object
properties:
pets:
type: array
items:
type: { $ref: "#/definitions/PetListResponse/Pet" }
required: [pets]
Pet:
name: Pet
type: object
properties:
id: { type: uuid }
name: { type: string }
tags:
type: set
items: { type: string }
required: [id, name]
¶
The $id and name at the aggregate root are assigned by the aggregating
tool. This document does not prescribe their form. How a namespace is then
rendered in a target language is likewise outside the scope of this document,
whether as a package, a module, a nested class, or a name prefix. What is
required is that the two Pet declarations stay distinguishable at the
schema level, so that the choice remains available.¶
An author who wants one Pet rather than two says so in the Description, by
giving the shared type its own resource and pulling it in with $import or
$importdefs (Section 5.6) instead of copying it. The import
names the namespace the copy lands in, so the scoping is the author's rather
than the aggregating tool's.¶
$uses
Under the extended meta-schema, non-Core add-ins are offered but not active.
A schema activates them through $uses, an array of add-in tokens declared
at the schema root. Under the validation meta-schema, all add-ins are active
by default.¶
Following the convention established by the add-in specifications themselves
[JSTRUCT-UNITS] [JSTRUCT-VALIDATION] [JSTRUCT-COMPOSITION], $uses is
placed at the root of the JSON Structure schema document to activate add-ins
offered by that document's own $schema meta-schema. Tooling conforming to
this binding MUST support $uses in that position.¶
This meta-schema-level usage is distinct from the document-instance $offers
and $uses mechanism JSON Structure Core also defines. There, a schema
document advertises its own custom add-in types through $offers, and a
separate JSON instance document that conforms to that schema selects among
them with its own top-level $uses [JSTRUCT-CORE]. The two share a keyword
name but operate in different scopes. This section's $uses selects add-ins
that the schema's own meta-schema offers to it. A JSON Structure Schema
Object embedded in an OpenAPI Description MAY additionally use $offers to
advertise custom add-in types to instance documents. Doing so has no effect
on this section's meta-schema-level $uses.¶
A custom meta-schema recognized under Section 4.4 MUST
explicitly document which add-ins it activates by default and which it only
offers for opt-in through $uses. "All add-ins" is not a well-defined active
set without a specific meta-schema to enumerate it against. Tooling MUST NOT
assume that a custom meta-schema's active and offered sets mirror those of
the canonical meta-schema it extends.¶
For example, JSONStructureUnits [JSTRUCT-UNITS] is offered, but not
active, under the extended meta-schema. A schema that wants to annotate
numeric properties with unit, ucumUnit, and symbol MUST list it in
$uses:¶
components:
schemas:
Measurement:
$schema: https://json-structure.org/meta/extended/v0/#
$uses:
- JSONStructureUnits
$id: https://api.example.com/schemas/measurement
name: Measurement
type: object
properties:
temperature:
type: double
ucumUnit: "Cel"
symbol: "°C"
¶
Omitting JSONStructureUnits from $uses while still using unit,
ucumUnit, or symbol leaves those keywords unrecognized under the extended
meta-schema. Tooling MUST treat them as unknown properties in that case, and
MUST NOT silently apply JSONStructureUnits semantics. Under the validation
meta-schema, JSONStructureUnits is active by default and $uses is not
required to enable it.¶
Processing a JSON Structure Schema Object separates memory layout from constraint checking:¶
The generated in-memory type SHOULD be derived only from the JSON
Structure Core structural keywords: type, name, properties, items,
values, tuple, choices, selector, abstract, $extends,
required, additionalProperties, and the precision, scale, and
maxLength storage constraints. Constraint keywords contributed by the
JSONStructureValidation [JSTRUCT-VALIDATION] and
JSONStructureConditionalComposition [JSTRUCT-COMPOSITION] add-ins are
runtime validation rules and SHOULD NOT change the generated type.¶
The selected meta-schema determines whether those add-ins are active. They
are opt-in through $uses under the extended meta-schema, and active by
default under the validation meta-schema. Conditional-composition keywords
are evaluated as boolean value validation
(Section 5.4.3), never as structural composition.¶
This separation lets a generator produce a deterministic memory layout from Core keywords while a gateway or server enforces the full validation and composition rules against the same schema.¶
This section supplies the data media types declaration required by Section 4.11. The declaration is none.¶
JSON Structure defines no message framing. It has no container format, no schema fingerprint, and no media type of its own. A body described by a JSON Structure Schema Object is carried in whatever media type its Media Type Object names, and this binding does not change how that media type frames the body. No schema travels with the body, so the Schema Object is the only schema, and the schema-on-the-wire requirements of Section 4.11 do not apply.¶
JSON Structure does fix how several values appear inside a JSON body. These representations cannot be inferred from the value, and a consumer that reads a JSON Structure Schema Object as though it were an OAS-dialect schema gets them wrong ([JSTRUCT-CORE]):¶
int64, uint64, int128, uint128, and decimal appear as JSON
strings. Interoperable JSON numbers span -2^53 to 2^53-1, and these types
exceed that range.¶
binary appears as a string, Base64 by default. contentEncoding selects
another alphabet, and contentCompression names a compression applied
before the encoding.¶
date, datetime, time, and duration appear as strings in the
corresponding RFC 3339 productions.¶
Tooling MUST apply these representations when it reads or writes a body of
application/json, or of any media type whose payload is JSON. Tooling MUST NOT emit an int64 as a JSON number. Tooling MUST NOT surface a quoted
int64 to an application as a string-typed value.¶
JSON Structure encoding specifications that map the type system onto a non-JSON wire format are separate specifications. Each is a separate dialect binding and declares its own data media types under Section 4.1. This document does not cover them.¶
This section supplies the type-determination procedure required by Section 4.10.¶
Given a starting-point JSON Structure Schema Object, tooling MUST determine the type of a value as follows. All steps operate on the schema alone.¶
Resolve the starting point to a type definition. Follow $root from a
resource root, and follow a type whose value is a $ref to the named
type it designates. Follow $extends to incorporate inherited members.
Imported namespaces are resolved first (Section 5.6), so a
$ref into an imported namespace is followed the same way.¶
Locate the value: a named member under properties, a positional element
under tuple, the item type under items, or the value type under
values.¶
The located declaration's type gives the JSON data type directly. JSON
Structure requires type on every type declaration, so this step does not
fail for a valid schema.¶
Map the JSON Structure type to a JSON data type. The types int64,
uint64, int128, uint128, and decimal map to string. JSON
Structure represents them as strings, since their ranges exceed the
interoperable range of a JSON number ([JSTRUCT-CORE]). The remaining
numeric types map to number: number, integer, int8, uint8,
int16, uint16, int32, uint32, float8, float, and double.
string, datetime, date, time, duration, uuid, uri, binary,
and jsonpointer map to string. boolean maps to boolean. object,
map, and choice map to object. array, set, and tuple map to
array. null maps to null.¶
Tooling MUST NOT map the large-integer and decimal types to number. Doing so produces an unquoted numeric literal in a form field, path segment, query parameter, or header value, which loses precision and does not match the JSON Structure representation of the same value.¶
A type union yields "undetermined" unless exactly one member of the union
remains after removing null, in which case that member's mapping applies.
A choice used at a position requiring a scalar serialization yields
"undetermined".¶
JSON Structure requires an explicit type everywhere and has no keyword that
makes a declaration apply conditionally to the data. This procedure therefore
never needs to inspect instance data, and never has to reconcile competing
type assertions the way the OAS-dialect procedure does.¶
Values that map to object or array are serialized as application/json by
default in an Encoding Object, exactly as for the OAS dialect. A binary
value carries no contentEncoding and is serialized as
application/octet-stream unless an Encoding Object's contentType says
otherwise.¶
The following components.schemas entry is a JSON Structure Schema Object.
Its $schema selects the JSON Structure Core dialect, so type,
properties, and the primitive type names are interpreted per JSON
Structure and not per the OAS dialect. It is referenced from elsewhere in
the OpenAPI Description with an ordinary OpenAPI $ref to
#/components/schemas/TelemetryMessage.¶
components:
schemas:
TelemetryMessage:
$schema: https://json-structure.org/meta/core/v0/#
$id: https://api.example.com/schemas/telemetry-message
name: TelemetryMessage
type: object
properties:
messageId:
type: uuid
timestamp:
type: datetime
sensorId:
type: string
reading:
type: double
¶
jsonSchemaDialect as the Document Default
An OpenAPI Object MAY set jsonSchemaDialect once, so that Schema Objects
need not repeat $schema individually (Section 4.3). Here the same
TelemetryMessage schema omits both its own $schema and its own $id. The
document default supplies the dialect, and the default-construction rule
(Section 4.5) supplies the identifier.¶
openapi: 3.1.0
jsonSchemaDialect: https://json-structure.org/meta/core/v0/#
info:
title: Telemetry API
version: "1.0.0"
components:
schemas:
TelemetryMessage:
name: TelemetryMessage
type: object
properties:
messageId:
type: uuid
reading:
type: double
¶
Assuming this Description is retrieved from
https://api.example.com/openapi.yaml, tooling that extracts
TelemetryMessage for standalone validation
(Section 4.6) MUST produce:¶
$schema: https://json-structure.org/meta/core/v0/#
$id: "https://api.example.com/openapi.yaml\
#/components/schemas/TelemetryMessage"
name: TelemetryMessage
type: object
properties:
messageId:
type: uuid
reading:
type: double
¶
A Schema Object that carries its own $schema or $id, as in
Section 6.1, overrides the corresponding default. The
two defaults compose independently per keyword. They are not an
all-or-nothing pair.¶
$import
Here TelemetryMessage imports the sibling CommonTypes library by its $id
into the Common namespace, extends the imported BaseMessage, and
references the imported GeoPoint. Both entries carry an $id, so the
import resolves within the Description without any network access.¶
components:
schemas:
CommonTypes:
$schema: https://json-structure.org/meta/core/v0/#
$id: https://api.example.com/schemas/common
name: CommonTypes
definitions:
BaseMessage:
name: BaseMessage
abstract: true
type: object
properties:
messageId: { type: uuid }
timestamp: { type: datetime }
GeoPoint:
name: GeoPoint
type: object
properties:
lat: { type: double }
long: { type: double }
TelemetryMessage:
$schema: https://json-structure.org/meta/extended/v0/#
$id: https://api.example.com/schemas/telemetry
name: TelemetryMessage
$root: "#/definitions/TelemetryMessage"
definitions:
Common:
$importdefs: https://api.example.com/schemas/common
TelemetryMessage:
name: TelemetryMessage
type: object
$extends: "#/definitions/Common/BaseMessage"
properties:
sensorId: { type: string }
reading: { type: double }
location:
type: { $ref: "#/definitions/Common/GeoPoint" }
¶
When the import URI matches no $id in the Description, it denotes an
external schema resource. Here OrderEvent imports an external people.json
type library by its absolute URI into the People namespace and uses the
imported Person type. The external document is a self-contained JSON
Structure schema and need not be an OpenAPI Description.¶
components:
schemas:
OrderEvent:
$schema: https://json-structure.org/meta/extended/v0/#
$id: https://api.example.com/schemas/order-event
name: OrderEvent
type: object
properties:
customer:
type: { $ref: "#/definitions/People/Person" }
definitions:
People:
$import: https://types.example.com/people.json
¶
A conforming JSON Structure Schema Object is a Schema Object that meets two
conditions. Its effective dialect is one of the canonical meta-schema URIs in
Section 5.2, or a separately configured derived URI
whose base dialect is one of those canonical URIs. Its content is a valid
JSON Structure schema for that meta-schema, including any add-ins it
activates through $uses.¶
The roles below are defined for any dialect binding (Section 4). The parenthetical requirements name both the generic rule and, where one exists, its JSON Structure instantiation. An implementation MAY conform to any subset of these roles. A component that fills only one role, such as a documentation renderer that never resolves imports, is not required to implement the others.¶
Parses an OpenAPI Description, classifies each Schema Object position as a Reference Object or a Schema Object (Section 4.2), and determines the effective dialect of every Schema Object (Section 4.3). A conforming Reader MUST classify before selecting a dialect, MUST recognize the dialect URIs its binding declares (Section 5.2), and MUST NOT process a Schema Object in a bound dialect as an OAS-dialect schema, or the reverse (Section 4.4).¶
Validates that the content of a Schema Object is well-formed in its effective dialect, including any vocabularies active through that dialect or opted into by the schema (Section 5.8). A conforming Schema Validator MUST reject a Schema Object that is not valid under its effective dialect and MUST NOT silently ignore unrecognized keywords to force validity.¶
Resolves the dialect's own references and cross-document mechanism per Section 4.7 and Section 4.8 (Section 5.5, Section 5.6). A conforming Resolver MUST resolve in-Description identities before any network retrieval, MUST reject cycles, and MUST enforce the limits of Section 8.¶
Validates instances, such as request and response bodies and parameters, against a Schema Object in its dialect. It applies both structural and constraint keywords (Section 5.9).¶
Produces an in-memory type or wire (de)serializer from a Schema Object. A conforming Codec or Code Generator MUST derive memory layout only from the dialect's structural keywords (Section 5.9). It MUST use the binding's type determination procedure for non-JSON serializations (Section 4.10, Section 5.11). It MUST reject any construct it does not support, and MUST NOT silently reinterpret that construct under OAS or JSON Schema semantics. Falling back to OAS-dialect interpretation of an unsupported construct is non-conforming. Different dialects assign different meaning to the same keywords (Section 5.4).¶
A tool that implements more than one role MUST keep the roles' failure modes distinct. A Schema Validator rejection MUST NOT be silently downgraded to a warning by a Codec operating on the same schema, and the reverse holds too. Across whichever roles it implements, conforming tooling MUST keep the OpenAPI and dialect reference layers separate (Section 4.7).¶
Conforming tooling SHOULD honor the code-generation and runtime-validation separation of Section 5.9.¶
A Description that contains only plain OAS Schema Objects is unaffected by this document and remains a conforming OpenAPI Description.¶
A dialect's cross-document mechanism can reference external URIs. For JSON
Structure that mechanism is $import and $importdefs. Tooling MUST
resolve in-Description identity matches before any network access, and MUST
use a secure transport such as HTTPS for external retrieval. Tooling MUST
constrain automatic retrieval with an allow-list of permitted hosts or URI
prefixes, connection and read timeouts, and response size limits. These
constraints prevent server-side request forgery and resource exhaustion
against internal or otherwise unintended targets. Network retrieval MUST be
disabled unless explicitly enabled (Section 4.8). Tooling
SHOULD cache retrieved schemas.¶
A schema resource retrieved from outside the Description is not part of the OAD (Section 4.8). It has therefore not passed whatever checks the deployment applies to OpenAPI documents. Tooling MUST subject such a resource to the same limits, transport requirements, and validation as any other externally retrieved document. Tooling MUST NOT grant it the trust extended to the entry document only because a schema in the Description named it.¶
Before expanding any import, tooling MUST construct the complete import
dependency graph reachable from a JSON Structure Schema Object, following
$import and $importdefs transitively through every resource it reaches.
Tooling MUST detect cycles in that graph before copying any definitions
from it. A cycle anywhere in the graph MUST cause the entire import to be
rejected atomically. Tooling MUST NOT apply a partial set of definitions
from a graph that is later found to be cyclic or otherwise invalid.
Tooling MUST also reject an import graph in which two distinct resources
declare the same $id, whether both are external or one is external and
one is in-Description. Such a graph is ambiguous. Tooling MUST treat it as
an import error and MUST NOT resolve it by source precedence. Tooling MUST
enforce finite, configurable upper bounds, and MUST reject an import graph
that exceeds them, on at least: the total number of distinct imported
resources; the total decoded byte size of all imported resources combined;
the import nesting depth from the importing resource; the fan-out (direct
import count) of any single resource; the decompression ratio of any
compressed transport encoding; and the number of HTTP redirects followed
per retrieval. Exceeding any bound MUST be treated as an import error
under [JSTRUCT-IMPORT], and MUST NOT be treated as a silent truncation.¶
JSON Schema, JSON Structure, and other dialects share keyword spellings
such as type, properties, and $ref, with differing semantics. Tooling
MUST NOT process a Schema Object in a bound dialect as an OAS-dialect
schema, or the reverse. Misidentifying the dialect can silently change
validation outcomes. The same hazard applies to type determination for
non-JSON serializations. Applying the OAS-dialect inspection procedure to
another dialect can silently produce the wrong wire type
(Section 4.10).¶
A schema retrieved from an external source is untrusted input. Tooling MUST validate it against its declared dialect before use and SHOULD apply the same input-handling precautions as for any other externally retrieved document.¶
A JSON Structure binary value MAY name a compression through
contentCompression, applied before the value is encoded
(Section 5.10). Decompression is driven by data on the
wire. Tooling MUST enforce a bound on the decompressed size of such a value
and on its decompression ratio, and MUST reject a value that exceeds either.
Tooling MUST reject a compression it has not been configured to accept.¶
Otherwise, the security considerations of OAS [OAS] apply unchanged.¶
This document has no IANA actions.¶
This appendix is informative except where it restates a requirement stated normatively elsewhere in this document.¶
For readers migrating a Schema Object from the OAS dialect, the following constructs change meaning:¶
| OAS / JSON Schema construct | JSON Structure equivalent |
|---|---|
type: string + format: date-time / uuid / binary
|
dedicated types datetime, uuid, binary
|
type: integer + format: int64
|
int64 (and the other precise numeric types); note that int64, uint64, int128, uint128, and decimal are represented as JSON strings, not numbers |
nullable: true
|
type union including "null"
|
discriminator + allOf for subtyping |
abstract + $extends
|
oneOf of variants + discriminator, variants unrelated |
choice with a choices map (tagged union, Section 5.4.2) |
oneOf of variants + discriminator, variants share a base |
choice with $extends and selector (inline union, Section 5.4.2) |
allOf / anyOf / oneOf as structural composition |
not used for composition; conditional-composition add-in is validation-only |
additionalProperties (boolean or schema) |
additionalProperties (unchanged: boolean, or a schema constraining additional members) |
required array |
required (unchanged; MAY also be an array of arrays for mutually exclusive alternative required sets) |
enum
|
enum (unchanged) |
const
|
const (unchanged) |
readOnly / writeOnly
|
not part of the JSON Structure type system; retained as annotations on individual properties entries |
deprecated on a property |
not part of the JSON Structure type system; retained as an annotation on that property |
$ref to another components.schemas entry |
$import / $importdefs (Section 5.6) |
The three OAS annotation keywords above remain usable when they annotate an
individual member inside properties. The JSON Structure member type
definitions permit additional properties beyond the JSON Structure keyword
set [JSTRUCT-CORE]. They MUST NOT be placed at the root of a JSON Structure
Schema Object, as in a deprecated: true describing the entire schema
resource. The root of a JSON Structure schema document is a closed object
type that permits only the keywords JSON Structure Core defines for it, and
validating such a root against its meta-schema fails if an OAS-only
annotation is added there. A resource-level deprecation notice MUST instead
be conveyed through description prose or an x--prefixed OAS specification
extension on the Schema Object. It MUST NOT be conveyed by adding
deprecated as a root sibling of $schema, type, or name.¶
TODO acknowledge.¶