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 2 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
85 changes: 83 additions & 2 deletions jsonschema-core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -571,9 +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.
allow tools to follow the correct behaviour. The "$recurse" keyword is
provided to facilitate this usage.
</t>
<t>
Note that the recursive nature of meta-schemas requires re-defining
Expand Down Expand Up @@ -904,6 +905,86 @@
</section>
</section>

<section title='Recursive references with "$recurse"'>
<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 schema document
where processing was initiated.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it MUST be treated as a reference to the schema document where processing was initiated.

vs

Note that even if processing began at a subschema within a document, the "$recurse" target MUST be the root of the document.

The first seems like its saying if we ref into a document at some lower level, it should recurse to THAT level, but the 2nd says it must be the root? Confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the key here is the wording "schema document" which is defined in section 4.3 in both draft-07 and the current HEAD commit. A schema document is a document, not a JSON object. So saying that it MUST be treated as a reference to the schema document where processing was initiated means exactly what the clarification says.

But I'm open to suggestion on how to word it better, preferably without entirely re-stating 4.3. Maybe just an <xref /> to it? I suspect the complete list of people who understand the distinction without having to look it consists of is @awwright and me.

</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 of the document.
</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[
{
"$id": "https://example.com/base",
"properties": {
"local": {
"$ref": "#",
},
"recursive": {
"$recurse": true
}
}
}

{
"$schema": "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

er... yeah, thanks!

"$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='Schema Re-Use With "$defs"'>
<t>
The "$defs" keyword provides a standardized location for schema
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
}