Skip to content

Add "$recurse" for extending recursive schemas #589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 0 additions & 49 deletions hyper-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,8 @@
"$schema": "http://json-schema.org/draft-08/hyper-schema#",
"$id": "http://json-schema.org/draft-08/hyper-schema#",
"title": "JSON Hyper-Schema",
"$defs": {
"schemaArray": {
"allOf": [
{ "$ref": "http://json-schema.org/draft-08/schema#/$defs/schemaArray" },
{
"items": { "$ref": "#" }
}
]
}
},
"allOf": [ { "$ref": "http://json-schema.org/draft-08/schema#" } ],
"properties": {
"additionalItems": { "$ref": "#" },
"additionalProperties": { "$ref": "#"},
"dependencies": {
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "type": "array" }
]
}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/$defs/schemaArray" }
]
},
"$defs": {
"additionalProperties": { "$ref": "#" }
},
"definitions": {
"$comment": "Renamed to $defs, but retained here to ensure compatibility",
"additionalProperties": { "$ref": "#" }
},
"patternProperties": {
"additionalProperties": { "$ref": "#" }
},
"properties": {
"additionalProperties": { "$ref": "#" }
},
"if": {"$ref": "#"},
"then": {"$ref": "#"},
"else": {"$ref": "#"},
"allOf": { "$ref": "#/$defs/schemaArray" },
"anyOf": { "$ref": "#/$defs/schemaArray" },
"oneOf": { "$ref": "#/$defs/schemaArray" },
"not": { "$ref": "#" },
"contains": { "$ref": "#" },
"propertyNames": { "$ref": "#" },

"base": {
"type": "string",
"format": "uri-template"
Expand Down
174 changes: 134 additions & 40 deletions jsonschema-core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@
</section>
</section>

<section title="JSON Schema Documents">
<section title="JSON Schema Documents" anchor="schema-documents">
<t>
A JSON Schema document, or simply a schema, is a JSON document used to describe
an instance.
Expand Down Expand Up @@ -571,14 +571,10 @@
</t>
<t>
Authors of extensions to JSON Schema are encouraged to write their own
meta-schemas, which extend the existing meta-schemas using "allOf".
meta-schemas, which MAY extend the existing meta-schemas using "allOf".
This extended meta-schema SHOULD be referenced using the "$schema" keyword, to
allow tools to follow the correct behaviour.
</t>
<t>
Note that the recursive nature of meta-schemas requires re-defining
recursive keywords in the extended meta-schema, as can be seen in
the JSON Hyper-Schema meta-schema.
allow tools to follow the correct behaviour. The "$recurse" keyword is
provided to facilitate this usage.
</t>
</section>

Expand Down Expand Up @@ -789,41 +785,137 @@
</section>
</section>

<section title='Schema References With "$ref"'>
<t>
The "$ref" keyword is used to reference a schema, and provides the ability to
validate recursive structures through self-reference.
</t>
<t>
An object schema with a "$ref" property MUST be interpreted as a "$ref" reference.
The value of the "$ref" property MUST be a URI Reference.
Resolved against the current URI base, it identifies the URI of a schema to use.
All other properties in a "$ref" object MUST be ignored.
</t>
<t>
The URI is not a network locator, only an identifier. A schema need not be
downloadable from the address if it is a network-addressable URL, and
implementations SHOULD NOT assume they should perform a network operation when they
encounter a network-addressable URI.
</t>
<section title="Schema References">
<t>
A schema MUST NOT be run into an infinite loop against a schema. For example, if two
schemas "#alice" and "#bob" both have an "allOf" property that refers to the other,
a naive validator might get stuck in an infinite recursive loop trying to validate
the instance.
Schemas SHOULD NOT make use of infinite recursive nesting like this; the behavior is
undefined.
Two keywords are provided for referencing one schema from another, and
for validating recursive structures through self-reference.
Both of these keywords behave as
<xref target="in-place">in-place applicators</xref>, except that the schema
being applied is identified rather than appearing as all or part of the
keyword's value.
</t>

<section title='Direct References with "$ref"'>
<t>
The "$ref" keyword is used to reference a statically identified schema.
</t>
<t>
An object schema with a "$ref" property MUST be interpreted as a "$ref"
reference. The value of the "$ref" property MUST be a URI Reference.
Resolved against the current URI base, it identifies the URI of a schema
to use. All other properties in a "$ref" object MUST be ignored.
</t>
<t>
The URI is not a network locator, only an identifier. A schema need not be
downloadable from the address if it is a network-addressable URL, and
implementations SHOULD NOT assume they should perform a network operation
when they encounter a network-addressable URI.
</t>
<t>
A schema MUST NOT be run into an infinite loop against an instance. For
example, if two schemas "#alice" and "#bob" both have an "allOf" property
that refers to the other, a naive validator might get stuck in an infinite
recursive loop trying to validate the instance. Schemas SHOULD NOT make
use of infinite recursive nesting like this; the behavior is undefined.
</t>
</section>

<section title='Recursive References with "$recurse"'>
<t>
The "$recurse" keyword is used to construct extensible recursive schemas.
</t>
<t>
This keyword's value MUST be the boolean literal true.
<cref>
Future drafts may extend the usage with other values. The immediate
use case does not require any targets other than the entry point
root, and a regular fragment URI reference does not provide the
correct semantics. Should other values be added in the future,
it is expected that a boolean true value will remain an alias for
this original use case.
</cref>
</t>
<t>
The presence of this keyword with a boolean true value indicates that,
during processing, it MUST be treated as a reference to the root of the
<xref target="schema-documents">schema document</xref> where processing
was initiated. The current base URI is not relevant to "$recurse".
</t>
<t>
This document, known as the entry point schema, is the schema document that
was initially supplied to the implementation, as opposed to schema documents
that were processed as a result of following a "$ref" reference. Note that
even if processing began at a subschema within a document, the "$recurse"
target MUST be the root schema.
</t>
<t>
Aside from the dynamic definition of the reference target, a "$recurse"
reference MUST behave identically to a "$ref" reference.
</t>
<figure>
<preamble>
Given the following schemas:
</preamble>
<artwork>
<![CDATA[
{
"$schema": "http://json-schema.org/draft-08/schema#",
"$id": "https://example.com/base",
"properties": {
"local": {
"$ref": "#",
},
"recursive": {
"$recurse": true
}
}
}

{
"$schema": "http://json-schema.org/draft-08/schema#",
"$id": "https://example.com/extension",
"properties": {
"extended": {
"$ref": "https://example.com/base",
}
}
}
]]>
</artwork>
</figure>
<t>
When an implementation begins processing with the
"https://example.com/base" schema, both the "local" and "recursive"
references resolve to "https://example.com/base". The entry point
schema and the schema being processed are the same.
</t>
<t>
However, when an implementation begins processing with the
"https://example.com/extension" schema, and processes the
"https://example.com/base" schema as a result of following the "$ref"
within the "extended" property, now the entry point schema is
"https://example.com/extension".
</t>
<t>
Therefore the "local" property's reference
still resolves to "https://example.com/base" while the "recursive"
property's reference now resolves to "https://example.com/extension".
This behavior remains the same even if the implementation begins processing
at "https://example.com/extension#/properties/extended".
</t>
</section>

<section title="Loading a referenced schema">
<t>
The use of URIs to identify remote schemas does not necessarily mean anything is downloaded,
but instead JSON Schema implementations SHOULD understand ahead of time which schemas they will be using,
and the URIs that identify them.
The use of URIs to identify remote schemas does not necessarily mean
anything is downloaded, but instead JSON Schema implementations SHOULD
understand ahead of time which schemas they will be using, and the URIs
that identify them.
</t>
<t>
When schemas are downloaded,
for example by a generic user-agent that doesn't know until runtime which schemas to download,
see <xref target="hypermedia">Usage for Hypermedia</xref>.
When schemas are downloaded, for example by a generic user-agent that
doesn't know until runtime which schemas to download, see
<xref target="hypermedia">Usage for Hypermedia</xref>.
</t>
<t>
Implementations SHOULD be able to associate arbitrary URIs with an arbitrary
Expand All @@ -849,8 +941,8 @@
</t>
<t>
If the resulting URI identifies a schema within the current document, or
within another schema document that has been made available to the implementation,
then that schema SHOULD be used automatically.
within another schema document that has been made available to the
implementation, then that schema SHOULD be used automatically.
</t>
<t>
For example, consider this schema:
Expand Down Expand Up @@ -1006,7 +1098,8 @@
</t>
</section>

<section title="Keywords for Applying Subschemas in Place">
<section title="Keywords for Applying Subschemas in Place"
anchor="in-place">
<t>
These keywords apply subschemas to the same location in the instance
as the parent schema is being applied. They allow combining
Expand Down Expand Up @@ -1644,6 +1737,7 @@ User-Agent: product-name/5.4.1 so-cool-json-schema/1.0.2 curl/7.43.0
<t>Moved "definitions" from the Validation specification here as "$defs"</t>
<t>Moved applicator keywords from the Validation specification as their own vocabulary</t>
<t>Moved "dependencies" from the Validation specification, but only the schema form</t>
<t>Added "$recurse" for dynamically evaluated recursive references</t>
</list>
</t>
<t hangText="draft-handrews-json-schema-01">
Expand Down
34 changes: 19 additions & 15 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
"items": { "$recurse": true }
},
"nonNegativeInteger": {
"type": "integer",
Expand Down Expand Up @@ -50,18 +50,22 @@
"type": "string",
"format": "uri-reference"
},
"$recurse": {
"type": "boolean",
"const": true
},
"$comment": {
"type": "string"
},
"$defs": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recurse": true },
"default": {}
},
"definitions": {
"$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.",
"type": "object",
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recurse": true },
"default": {}
},
"title": {
Expand Down Expand Up @@ -101,10 +105,10 @@
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"additionalItems": { "$recurse": true },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$recurse": true },
{ "$ref": "#/$defs/schemaArray" }
],
"default": true
Expand All @@ -115,32 +119,32 @@
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"contains": { "$recurse": true },
"maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
"minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/$defs/stringArray" },
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recurse": true },
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recurse": true },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recurse": true },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$recurse": true },
{ "$ref": "#/$defs/stringArray" }
]
}
},
"propertyNames": { "$ref": "#" },
"propertyNames": { "$recurse": true },
"const": true,
"enum": {
"type": "array",
Expand All @@ -162,13 +166,13 @@
"format": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"if": {"$ref": "#"},
"then": {"$ref": "#"},
"else": {"$ref": "#"},
"if": {"$recurse": true},
"then": {"$recurse": true},
"else": {"$recurse": true},
"allOf": { "$ref": "#/$defs/schemaArray" },
"anyOf": { "$ref": "#/$defs/schemaArray" },
"oneOf": { "$ref": "#/$defs/schemaArray" },
"not": { "$ref": "#" }
"not": { "$recurse": true }
},
"default": true
}