JSON Structure OAS Binding August 2026
Vasters Standards Track [Page]
Workgroup:
Building Blocks for HTTP APIs
Published:
Author:
C. Vasters
Microsoft Corporation

JSON Structure: OpenAPI Binding

Abstract

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.

About This Document

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.

Table of Contents

1. Introduction

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:

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.

2. Conventions and Terminology

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:

Schema Object:

An OpenAPI Schema Object as defined by the OpenAPI Specification's Schema Object section [OAS].

Dialect, meta-schema:

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].

JSON Structure Schema Object:

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.

Schema resource:

A self-contained JSON Structure schema identified by an $id, within which $ref, $extends, and $import are resolved.

Add-in:

An optional JSON Structure keyword vocabulary (for example JSONStructureValidation, JSONStructureUnits) that a schema activates through $uses, subject to what its meta-schema offers.

Description:

An OpenAPI Description (OAD) [OAS]: the complete set of documents describing an API.

3. Scope and Relationship to the OpenAPI Specification

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.

4. Dialect Binding Requirements

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.

4.1. Binding Parameters

A dialect binding MUST declare the following. These declarations are the only dialect-specific inputs the rest of this part requires.

Table 1
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.

4.2. Reference Object Classification

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.

4.3. Dialect Selection

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]):

  1. If the Schema Object is a schema-resource root and carries $schema, that value is the dialect.

  2. Otherwise, the OpenAPI Object's jsonSchemaDialect value, if present, is the dialect.

  3. 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.

4.4. Recognizing and Rejecting Dialects

  • 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.

4.5. Default Resource Identity

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]):

  1. the OpenAPI Object's $self field, if present (OAS Appendix F.1);

  2. otherwise, the base URI of an encapsulating entity, if any, such as a multipart/related archive (OAS Appendix F.2);

  3. otherwise, the Description's retrieval URI: the URI it was actually fetched from (OAS Appendix F.3);

  4. 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:

  1. Take each JSON Pointer reference-token (for example components, schemas, TelemetryMessage) as a Unicode string and encode it as [RFC3629] UTF-8 octets.

  2. 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.

  3. 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.

  4. 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:

Table 2
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.

4.6. Materializing Defaults for Standalone Processing

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.

4.7. Reference Layer Separation

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.

4.8. Resolving Cross-Document References

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:

  1. a registered identity within the Description;

  2. a caller-supplied registry or cache mapping the reference URI to a previously retrieved schema resource, without performing network access;

  3. 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.

4.9. Type Identity and Scope

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.

4.10. Schema Inspection for Non-JSON Serializations

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.

4.11. Dialect-Defined Body Encodings

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.

4.12. Validating the Description Itself

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.

5. The JSON Structure Binding

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.

5.1. JSON Structure Binding Parameters

Table 3
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.

5.2. JSON Structure Meta-Schema URIs

The following URIs identify the JSON Structure dialects:

Table 4
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.

5.3. Materializing 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:

  1. 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.

  2. 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.

  3. 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:

Table 5
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.

5.4. Type System

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.

5.4.1. Inheritance

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.

5.4.2. Discriminated Unions

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.

5.4.3. Conditional Composition

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.

5.5. JSON Structure Reference Layer

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.

5.5.1. $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.

5.6. Cross-Schema Reuse with $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.

5.7. Namespaces Across Schema Resources

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.

5.8. Activating Add-ins with $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.

5.9. Code Generation and Runtime Validation

Processing a JSON Structure Schema Object separates memory layout from constraint checking:

Code generation:

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.

Runtime validation:

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.

5.10. Data Media Types and JSON Representation

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.

5.11. Type Determination for Non-JSON Serializations

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.

  1. 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.

  2. Locate the value: a named member under properties, a positional element under tuple, the item type under items, or the value type under values.

  3. 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.

  4. 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.

  5. 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.

6. Examples

6.1. A Single JSON Structure Schema Object

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

6.2. Using 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.

6.3. Cross-Schema Reuse with $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" }

6.4. Importing an External Type Library

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

7. Conformance

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.

Reader:

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).

Schema Validator:

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.

Resolver:

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.

Instance Validator:

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).

Codec / Code Generator:

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.

8. Security Considerations

Cross-document retrieval:

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.

Resources outside the OpenAPI Description:

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.

Cyclic and pathological cross-document graphs:

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.

Dialect confusion:

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).

Untrusted schemas:

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.

Compressed binary values:

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.

9. IANA Considerations

This document has no IANA actions.

10. Normative References

[JSTRUCT-ALTNAMES]
Vasters, C., "JSON Structure Alternate Names", n.d., <https://json-structure.github.io/alternate-names/draft-vasters-json-structure-alternate-names.html>.
[JSTRUCT-COMPOSITION]
Vasters, C., "JSON Structure Conditional Composition", n.d., <https://json-structure.github.io/conditional-composition/draft-vasters-json-structure-cond-composition.html>.
[JSTRUCT-CORE]
Vasters, C., "JSON Structure Core", n.d., <https://json-structure.github.io/core/draft-vasters-json-structure-core.html>.
[JSTRUCT-IMPORT]
Vasters, C., "JSON Structure Import", n.d., <https://json-structure.github.io/import/draft-vasters-json-structure-import.html>.
[JSTRUCT-UNITS]
Vasters, C., "JSON Structure: Symbols, Scientific Units, and Currencies", n.d., <https://json-structure.github.io/units/draft-vasters-json-structure-units.html>.
[JSTRUCT-VALIDATION]
Vasters, C., "JSON Structure Validation", n.d., <https://json-structure.github.io/validation/draft-vasters-json-structure-validation.html>.
[OAS]
OpenAPI Initiative, "OpenAPI Specification", , <https://spec.openapis.org/oas/latest>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC3629]
Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, , <https://www.rfc-editor.org/rfc/rfc3629>.
[RFC3986]
Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, , <https://www.rfc-editor.org/rfc/rfc3986>.
[RFC6901]
Bryan, P., Ed., Zyp, K., and M. Nottingham, Ed., "JavaScript Object Notation (JSON) Pointer", RFC 6901, DOI 10.17487/RFC6901, , <https://www.rfc-editor.org/rfc/rfc6901>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/rfc/rfc8174>.
[RFC8259]
Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, , <https://www.rfc-editor.org/rfc/rfc8259>.

Appendix A. Migrating a Schema Object from the OAS Dialect

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:

Table 6
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.

Acknowledgments

TODO acknowledge.

Author's Address

Clemens Vasters
Microsoft Corporation