diff --git a/hyper-schema.json b/hyper-schema.json index 28f9ad4f..484af6c5 100644 --- a/hyper-schema.json +++ b/hyper-schema.json @@ -10,7 +10,7 @@ "https://json-schema.org/draft/2019-09/vocab/content": true, "https://json-schema.org/draft/2019-09/vocab/hyper-schema": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "JSON Hyper-Schema", "allOf": [ diff --git a/jsonschema-core.xml b/jsonschema-core.xml index d25b23ee..6471c543 100644 --- a/jsonschema-core.xml +++ b/jsonschema-core.xml @@ -623,11 +623,9 @@ Keywords MAY be defined with a partial value, such as a URI-reference, which must be resolved against another value, such as another URI-reference or a full URI, which is found through the lexical - structure of the JSON document. The "$id" core keyword and - the "base" JSON Hyper-Schema keyword are examples of this sort - of behavior. Additionally, "$ref" and "$recursiveRef" from - this specification resolve their values in this way, although - they do not change how further values are resolved. + structure of the JSON document. The "$id", "$ref", and + "$dynamicRef" core keywords, and the "base" JSON Hyper-Schema + keyword, are examples of this sort of behavior. Note that some keywords, such as "$schema", apply to the lexical @@ -640,12 +638,12 @@ with an instance document. The outermost dynamic scope is the root schema of the schema document in which processing begins. The path from this root schema to any particular keyword (that - includes any "$ref" and "$recursiveRef" keywords that may have + includes any "$ref" and "$dynamicRef" keywords that may have been resolved) is considered the keyword's "validation path." Or should this be the schema object at which processing begins, even if it is not a root? This has some implications - for the case where "$recursiveAnchor" is only allowed in the + for the case where "$dynamicAnchor" is only allowed in the root schema but processing begins in a subschema. @@ -660,8 +658,8 @@ dynamic parent, rather than examining the local lexically enclosing parent. - The concept of dynamic scope is primarily used with "$recursiveRef" and - "$recursiveAnchor", and should be considered an advanced feature + The concept of dynamic scope is primarily used with "$dynamicRef" and + "$dynamicAnchor", and should be considered an advanced feature and used with caution when defining additional keywords. It also appears when reporting errors and collected annotations, as it may be possible to revisit the same lexical scope repeatedly with different dynamic @@ -723,8 +721,9 @@ While custom identifier keywords are possible, vocabulary designers should take care not to disrupt the functioning of core keywords. For example, - the "$recursiveAnchor" keyword in this specification limits its URI resolution - effects to the matching "$recursiveRef" keyword, leaving "$ref" undisturbed. + the "$dynamicAnchor" keyword in this specification limits its URI resolution + effects to the matching "$dynamicRef" keyword, leaving the behavior + of "$ref" undisturbed.
@@ -775,7 +774,7 @@ For some by-reference applicators, such as "$ref", the referenced schema can be determined by static analysis of the schema document's lexical scope. Others, - such as "$recursiveRef" and "$recursiveAnchor", may make use of dynamic + such as "$dynamicRef" (with "$dynamicAnchor"), may make use of dynamic scoping, and therefore only be resolvable in the process of evaluating the schema with an instance. @@ -1377,8 +1376,7 @@
-
+
Using JSON Pointer fragments requires knowledge of the structure of the schema. When writing schema documents with the intention to provide re-usable @@ -1387,8 +1385,32 @@ without requiring JSON Pointer references to be updated. - The "$anchor" keyword is used to specify such a fragment. It is an - identifier keyword that can only be used to create plain name fragments. + The "$anchor" and "$dynamicAnchor" keywords are used to specify such + fragments. They are identifier keywords that can only be used to create + plain name fragments, rather than absolute URIs as seen with "$id". + The behavior of the created fragment is identical for both keywords. + + + The base URI to which the resulting fragment is appended is the canonical + URI of the schema resource containing the "$anchor" or "$dynamicAnchor" + in question. As discussed in the previous section, this is either the + nearest "$id" in the same or parent schema object, or the base URI + for the document as determined according to RFC 3986. + + + Separately from the usual usage of URIs, "$dynamicAnchor" + indicates that the fragment is an extension point when used with + the "$dynamicRef" keyword. This low-level, advanced feature + makes it easier to extend recursive schemas such as the meta-schemas, + without imposing any particular semantics on that extension. + See the section on "$dynamicRef" + for details. + + + In most cases, the normal fragment behavior both suffices and + is more intuitive. Therefore it is RECOMMENDED that "$anchor" + be used to create plain name fragments unless there is a clear + need for "$dynamicAnchor". If present, the value of this keyword MUST be a string and MUST start with @@ -1403,12 +1425,9 @@ - The base URI to which the resulting fragment is appended is determined - by the "$id" keyword as explained in the previous section. - Two "$anchor" keywords in the same schema document MAY have the same - value if they apply to different base URIs, as the resulting full URIs - will be distinct. However, the effect of two "$anchor" keywords with the - same value and the same base URI is undefined. Implementations MAY + The effect of specifying the same fragment name multiple times within + the same resource, using any combination of "$anchor" and/or + "$dynamicAnchor", is undefined. Implementations MAY raise an error if such usage is detected.
@@ -1416,13 +1435,11 @@
Several keywords can be used to reference a schema which is to be applied to the - current instance location. "$ref" and "$recursiveRef" are applicator - keywords, applying the referenced schema to the instance. "$recursiveAnchor" - is an identifier keyword that controls how the base URI for resolving - the URI-reference value of "$recursiveRef is determined. + current instance location. "$ref" and "$dynamicRef" are applicator + keywords, applying the referenced schema to the instance. - As the values of "$ref" and "$recursiveRef" are URI References, this allows + As the values of "$ref" and "$dynamicRef" are URI References, this allows the possibility to externalise or divide a schema across multiple files, and provides the ability to validate recursive structures through self-reference. @@ -1447,103 +1464,59 @@ The value of the "$ref" property MUST be a string which is a URI-Reference. Resolved against the current URI base, it produces the URI of the schema - to apply. + to apply. This resolution is safe to perform on schema load, as the + process of evaluating an instance cannot change how the reference resolves.
-
+
- The "$recursiveRef" and "$recursiveAnchor" keywords are used to construct - extensible recursive schemas. A recursive schema is one that has - a reference to its own root, identified by the empty fragment - URI reference ("#"). + The "$dynamicRef" keyword is an applicator that allows for deferring the + full resolution until runtime, at which point it is resolved each time it is + encountered while evaluating an instance. + + + Together with "$dynamicAnchor", "$dynamicRef" implements a cooperative + extension mechanism that is primarily useful with recursive schemas + (schemas that reference themselves). Both the extension point and the + runtime-determined extension target are defined with "$dynamicAnchor", + and only exhibit runtime dynamic behavior when referenced with + "$dynamicRef". + + + The value of the "$dynamicRef" property MUST be a string which is + a URI-Reference. Resolved against the current URI base, it produces + the URI used as the starting point for runtime resolution. This initial + resolution is safe to perform on schema load. + + + If the initially resolved starting point URI includes a fragment that + was created by the "$dynamicAnchor" keyword, the initial URI MUST be + replaced by the URI (including the fragment) for the outermost schema + resource in the dynamic scope that defines + an identically named fragment with "$dynamicAnchor". + + Requiring both the initial and final URI fragment to be defined + by "$dynamicAnchor" ensures that the more common "$anchor" + never unexpectedly changes the dynamic resolution process + due to a naming conflict across resources. Users of + "$dynamicAnchor" are expected to be aware of the possibility + of such name collisions, while users of "$anchor" are not. + - Simply stated, a "$recursiveRef" behaves identically to "$ref", except - when its target schema contains "$recursiveAnchor" with a value of true. - In that case, the dynamic scope is examined to determine a new base URI, - and the URI-reference in "$recursiveRef" is re-evaluated against that - base URI. Unlike base URI changes with "$id", changes with - "$recursiveAnchor" are calculated each time a "$recursiveRef" is - resolved, and do not impact any other keywords. + Otherwise, its behavior is identical to "$ref", and no runtime + resolution is needed. - For an example using these keyword, see appendix + For a full example using these keyword, see appendix . - The difference between the hyper-schema meta-schema in previous + The difference between the hyper-schema meta-schema in pre-2019 drafts and an this draft dramatically demonstrates the utility of these keywords. -
- - The value of the "$recursiveRef" property MUST be a string which is - a URI-reference. It is a by-reference applicator that uses - a dynamically calculated base URI to resolve its value. - - - The behavior of this keyword is defined only for the value "#". - Implementations MAY choose to consider other values to be errors. - - This restriction may be relaxed in the future, but to date only - the value "#" has a clear use case. - - - - The value of "$recursiveRef" is initially resolved against the - current base URI, in the same manner as for "$ref". - - - The schema identified by the resulting URI is examined for the - presence of "$recursiveAnchor", and a new base URI is calculated - as described for that keyword in the following section. - - - Finally, the value of "$recursiveRef" is resolved against the - new base URI determined according to "$recursiveAnchor" producing - the final resolved reference URI. - - - Note that in the absence of "$recursiveAnchor" (and in some cases - when it is present), "$recursiveRef"'s behavior is identical to - that of "$ref". - - - As with "$ref", the results of this keyword are the results of the - referenced schema. - -
-
- - The value of the "$recursiveAnchor" property MUST be a boolean. - - - "$recursiveAnchor" is used to dynamically identify a base URI - at runtime for "$recursiveRef" by marking where such a calculation - can start, and where it stops. This keyword MUST NOT affect the - base URI of other keywords, unless they are explicitly defined - to rely on it. - - - If set to true, then when the containing schema object is used - as a target of "$recursiveRef", a new base URI is determined - by examining the dynamic scope for - the outermost schema that also contains "$recursiveAnchor" - with a value of true. The base URI of that schema is then used - as the dynamic base URI. - - - If no such schema exists, then the base URI is unchanged. - - - If this keyword is set to false, the base URI is unchanged. - - - Omitting this keyword has the same behavior as a value of false. - -
@@ -2670,7 +2643,7 @@ The relative location of the validating keyword that follows the validation path. The value MUST be expressed as a JSON Pointer, and it MUST include - any by-reference applicators such as "$ref" or "$recursiveRef". + any by-reference applicators such as "$ref" or "$dynamicRef".
@@ -2693,7 +2666,7 @@ The absolute, dereferenced location of the validating keyword. The value MUST be expressed as an absolute URI using the canonical URI of the relevant schema object, and it MUST NOT include by-reference applicators - such as "$ref" or "$recursiveRef" as non-terminal path components. + such as "$ref" or "$dynamicRef" as non-terminal path components. It MAY end in such keywords if the error or annotation is for that keyword, such as an unresolvable reference. @@ -3461,7 +3434,7 @@ https://example.com/schemas/common#/$defs/count/minimum { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://example.com/tree", - "$recursiveAnchor": true, + "$dynamicAnchor": "node", "type": "object", "properties": { @@ -3469,7 +3442,7 @@ https://example.com/schemas/common#/$defs/count/minimum "children": { "type": "array", "items": { - "$recursiveRef": "#" + "$dynamicRef": "#node" } } } @@ -3479,7 +3452,7 @@ https://example.com/schemas/common#/$defs/count/minimum { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://example.com/strict-tree", - "$recursiveAnchor": true, + "$dynamicAnchor": node, "$ref": "tree", "unevaluatedProperties": false @@ -3492,32 +3465,67 @@ https://example.com/schemas/common#/$defs/count/minimum ]]>
+ + When we load these two schemas, we will notice the "$dynamicAnchor" + named "node" (note the lack of "#" as this is just the name) + present in each, resulting in the following full schema URIs: + + "https://example.com/tree#node" + "https://example.com/strict-tree#node" + + In addition, JSON Schema implementations keep track of the fact + that these fragments were created with "$dynamicAnchor". + If we apply the "strict-tree" schema to the instance, we will follow the "$ref" to the "tree" schema, examine its "children" subschema, - and find the "$recursiveAnchor" in its "items" subschema. + and find the "$dynamicRef": to "#node" (note the "#" for URI fragment syntax) + in its "items" subschema. That reference resolves to + "https://example.com/tree#node", which is a URI with a fragment + created by "$dynamicAnchor". Therefore we must examine the dynamic + scope before following the reference. + + At this point, the dynamic path is - "#/$ref/properties/children/items/$recursiveRef". + "#/$ref/properties/children/items/$dynamicRef", with a dynamic scope + containing (from the outermost scope to the innermost): + + "https://example.com/strict-tree#" + "https://example.com/tree#" + "https://example.com/tree#/properties/children" + "https://example.com/tree#/properties/children/items" + - The base URI at this point is "https://example.com/tree", so the - "$recursiveRef" initially resolves to "https://example.com/tree#". - Since "$recursiveAnchor" is true, we examine the dynamic path to - see if there is a different base URI to use. We find - "$recursiveAnchor" with a true value at the dynamic paths of - "#" and "#/$ref". + Since we are looking for a plain name fragment, which can be + defined anywhere within a schema resource, the JSON Pointer fragments + are irrelevant to this check. That means that we can remove those + fragments and eliminate consecutive duplicates, producing: + + "https://example.com/strict-tree" + "https://example.com/tree" + - The outermost is "#", which is the root schema of the "strict-tree" - schema, so we use its base URI of "https://example.com/strict-tree", - which produces a final resolved URI of - "https://example.com/strict-tree#" for the "$recursiveRef". + In this case, the outermost resource also has a "node" fragment + defined by "$dynamicAnchor". Therefore instead of resolving the + "$dynamicRef" to "https://example.com/tree#node", we resolve it to + "https://example.com/strict-tree#node". This way, the recursion in the "tree" schema recurses to the root of "strict-tree", instead of only applying "strict-tree" to the instance root, but applying "tree" to instance children. + + This example shows both "$dynamicAnchor"s in the same place + in each schema, specifically the resource root schema. + Since plain-name fragments are independent of the JSON structure, + this would work just as well if one or both of the node schema objects + were moved under "$defs". It is the matching "$dynamicAnchor" values + which tell us how to resolve the dynamic reference, not any sort of + correlation in JSON structure. +
@@ -3560,8 +3568,8 @@ https://example.com/schemas/common#/$defs/count/minimum appropriate for certain use cases. - The recursive nature of meta-schemas makes the "$recursiveAnchor" - and "$recursiveRef" keywords particularly useful for extending + The recursive nature of meta-schemas makes the "$dynamicAnchor" + and "$dynamicRef" keywords particularly useful for extending existing meta-schemas, as can be seen in the JSON Hyper-Schema meta-schema which extends the Validation meta-schema. @@ -3617,7 +3625,7 @@ https://example.com/schemas/common#/$defs/count/minimum { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://example.com/meta/general-use-example", - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/core": true, "https://json-schema.org/draft/2019-09/vocab/applicator": true, @@ -3652,7 +3660,7 @@ https://example.com/schemas/common#/$defs/count/minimum { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://example.com/meta/example-vocab", - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "$vocabulary": { "https://example.com/vocab/example-vocab": true, }, @@ -3793,9 +3801,9 @@ https://example.com/schemas/common#/$defs/count/minimum Array-value "items" functionality is now "prefixItems" "items" subsumes the old function of "additionalItems" "contains" and "unevaluatedItems" interactions now specified - - - + Rename $recursive* to $dynamic* + $dynamicAnchor defines a fragment like $anchor + $dynamic* (previously $recursive) no longer use runtime base URI determination diff --git a/links.json b/links.json index 7b1a578f..07ad7a42 100644 --- a/links.json +++ b/links.json @@ -2,6 +2,7 @@ "$schema": "https://json-schema.org/draft/2019-09/hyper-schema", "$id": "https://json-schema.org/draft/2019-09/links", "title": "Link Description Object", + "allOf": [ { "required": [ "rel", "href" ] }, { "$ref": "#/$defs/noRequiredFields" } @@ -36,7 +37,7 @@ "format": "uri-template" }, "hrefSchema": { - "$recursiveRef": "https://json-schema.org/draft/2019-09/hyper-schema", + "$dynamicRef": "https://json-schema.org/draft/2019-09/hyper-schema#meta", "default": false }, "templatePointers": { @@ -63,7 +64,7 @@ "type": "string" }, "targetSchema": { - "$recursiveRef": "https://json-schema.org/draft/2019-09/hyper-schema", + "$dynamicRef": "https://json-schema.org/draft/2019-09/hyper-schema#meta", "default": true }, "targetMediaType": { @@ -71,7 +72,7 @@ }, "targetHints": { }, "headerSchema": { - "$recursiveRef": "https://json-schema.org/draft/2019-09/hyper-schema", + "$dynamicRef": "https://json-schema.org/draft/2019-09/hyper-schema#meta", "default": true }, "submissionMediaType": { @@ -79,7 +80,7 @@ "default": "application/json" }, "submissionSchema": { - "$recursiveRef": "https://json-schema.org/draft/2019-09/hyper-schema", + "$dynamicRef": "https://json-schema.org/draft/2019-09/hyper-schema#meta", "default": true }, "$comment": { diff --git a/meta/applicator.json b/meta/applicator.json index 50052914..6b4ac989 100644 --- a/meta/applicator.json +++ b/meta/applicator.json @@ -4,47 +4,47 @@ "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/applicator": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "Applicator vocabulary meta-schema", "properties": { "prefixItems": { "$ref": "#/$defs/schemaArray" }, - "items": { "$recursiveRef": "#" }, - "unevaluatedItems": { "$recursiveRef": "#" }, - "contains": { "$recursiveRef": "#" }, - "additionalProperties": { "$recursiveRef": "#" }, - "unevaluatedProperties": { "$recursiveRef": "#" }, + "items": { "$dynamicRef": "#meta" }, + "unevaluatedItems": { "$dynamicRef": "#meta" }, + "contains": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "#meta" }, + "unevaluatedProperties": { "$dynamicRef": "#meta" }, "properties": { "type": "object", - "additionalProperties": { "$recursiveRef": "#" }, + "additionalProperties": { "$dynamicRef": "#meta" }, "default": {} }, "patternProperties": { "type": "object", - "additionalProperties": { "$recursiveRef": "#" }, + "additionalProperties": { "$dynamicRef": "#meta" }, "propertyNames": { "format": "regex" }, "default": {} }, "dependentSchemas": { "type": "object", "additionalProperties": { - "$recursiveRef": "#" + "$dynamicRef": "#meta" } }, - "propertyNames": { "$recursiveRef": "#" }, - "if": { "$recursiveRef": "#" }, - "then": { "$recursiveRef": "#" }, - "else": { "$recursiveRef": "#" }, + "propertyNames": { "$dynamicRef": "#meta" }, + "if": { "$dynamicRef": "#meta" }, + "then": { "$dynamicRef": "#meta" }, + "else": { "$dynamicRef": "#meta" }, "allOf": { "$ref": "#/$defs/schemaArray" }, "anyOf": { "$ref": "#/$defs/schemaArray" }, "oneOf": { "$ref": "#/$defs/schemaArray" }, - "not": { "$recursiveRef": "#" } + "not": { "$dynamicRef": "#meta" } }, "$defs": { "schemaArray": { "type": "array", "minItems": 1, - "items": { "$recursiveRef": "#" } + "items": { "$dynamicRef": "#meta" } } } } diff --git a/meta/content.json b/meta/content.json index f6752a8e..e69ccdfa 100644 --- a/meta/content.json +++ b/meta/content.json @@ -4,7 +4,7 @@ "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/content": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "Content vocabulary meta-schema", @@ -12,6 +12,6 @@ "properties": { "contentMediaType": { "type": "string" }, "contentEncoding": { "type": "string" }, - "contentSchema": { "$recursiveRef": "#" } + "contentSchema": { "$dynamicRef": "#meta" } } } diff --git a/meta/core.json b/meta/core.json index 3d5311bf..3a84feb1 100644 --- a/meta/core.json +++ b/meta/core.json @@ -4,7 +4,7 @@ "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/core": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "Core vocabulary meta-schema", "type": ["object", "boolean"], @@ -51,7 +51,7 @@ }, "$defs": { "type": "object", - "additionalProperties": { "$recursiveRef": "#" }, + "additionalProperties": { "$dynamicRef": "#meta" }, "default": {} } } diff --git a/meta/format.json b/meta/format.json index 09bbfdda..68ad5b0d 100644 --- a/meta/format.json +++ b/meta/format.json @@ -4,7 +4,7 @@ "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/format": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "Format vocabulary meta-schema", "type": ["object", "boolean"], diff --git a/meta/hyper-schema.json b/meta/hyper-schema.json index 3d230589..f36d3477 100644 --- a/meta/hyper-schema.json +++ b/meta/hyper-schema.json @@ -4,7 +4,7 @@ "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/hyper-schema": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "JSON Hyper-Schema Vocabulary Schema", "type": ["object", "boolean"], diff --git a/meta/meta-data.json b/meta/meta-data.json index da04cff6..bbb171e9 100644 --- a/meta/meta-data.json +++ b/meta/meta-data.json @@ -4,7 +4,7 @@ "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/meta-data": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "Meta-data vocabulary meta-schema", diff --git a/meta/validation.json b/meta/validation.json index 9f59677b..75ba4522 100644 --- a/meta/validation.json +++ b/meta/validation.json @@ -4,7 +4,7 @@ "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/validation": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "Validation vocabulary meta-schema", "type": ["object", "boolean"], diff --git a/schema.json b/schema.json index 2248a0c8..4d4e8782 100644 --- a/schema.json +++ b/schema.json @@ -9,7 +9,7 @@ "https://json-schema.org/draft/2019-09/vocab/format": false, "https://json-schema.org/draft/2019-09/vocab/content": true }, - "$recursiveAnchor": true, + "$dynamicAnchor": "meta", "title": "Core and Validation specifications meta-schema", "allOf": [ @@ -25,7 +25,7 @@ "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": { "$recursiveRef": "#" }, + "additionalProperties": { "$dynamicRef": "#meta" }, "default": {} }, "dependencies": { @@ -33,7 +33,7 @@ "type": "object", "additionalProperties": { "anyOf": [ - { "$recursiveRef": "#" }, + { "$dynamicRef": "#meta" }, { "$ref": "meta/validation#/$defs/stringArray" } ] }