From 7fc2c06d580045271dd164849a70402ac483b304 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Mon, 22 Dec 2025 09:32:26 +1300 Subject: [PATCH 1/2] $dynamicRef uses plain-name --- specs/jsonschema-core.md | 81 ++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/specs/jsonschema-core.md b/specs/jsonschema-core.md index a847d1df..ef999642 100644 --- a/specs/jsonschema-core.md +++ b/specs/jsonschema-core.md @@ -1022,23 +1022,16 @@ just a fragment identifier not an IRI reference. To reference the "foo" `$anchor` from the same schema resource, you would use the fragment-only IRI `#foo`. See below for full examples. -`$dynamicAnchor` defines a different kind of fragment identifier that only has -meaning when used with `$dynamicRef`. It's not a normal fragment identifier and -therefore can't be used anywhere other than `$dynamicRef`. Normal [fragment -identifiers](https://www.rfc-editor.org/rfc/rfc3986#section-3.5) identify the -secondary resource (the subschema) while the rest of the IRI identifies the -primary resource (the schema resource). The fragment identifiers defined by -`$dynamicAnchor` are not normal fragment identifies because they identify both -the primary resource and the secondary resource. See {{dynamic-ref}} for -details. +`$dynamicAnchor` defines a (non-fragment) identifier that only has meaning when +used with `$dynamicRef`. If present, the value of these keywords MUST be a string and MUST conform to the plain name fragment identifier syntax defined in {{fragments}}. -`$anchor`, `$dynamicAnchor`, and any extensions that define a plain name -fragment identifiers MUST match XML's [`NCName` -production](https://www.w3.org/TR/2006/REC-xml-names11-20060816/#NT-NCName). For -convenience, the `NCName` syntax is reproduced here in ABNF form, using a +`$anchor`, `$dynamicAnchor`, and any extensions that define plain name +fragment identifiers MUST match XML's +[`NCName` production](https://www.w3.org/TR/2006/REC-xml-names11-20060816/#NT-NCName). +For convenience, the `NCName` syntax is reproduced here in ABNF form, using a minimal set of rules: ```abnf @@ -1058,9 +1051,9 @@ NCNameChar = NCNameStartChar / "-" / "." / DIGIT A schema MAY (and likely will) have multiple IRIs, but there is no way for an IRI to identify more than one schema. When multiple schemas attempt to identify -as the same IRI through the use of `$id`, `$anchor`, `$dynamicAnchor`, or any -other mechanism, implementations SHOULD raise an error condition. Otherwise the -result is undefined, and even if documented will not be interoperable. +as the same IRI through the use of `$id`, `$anchor`, or any other mechanism, +implementations SHOULD raise an error condition. Otherwise the result is +undefined, and even if documented will not be interoperable. #### Schema References {#references} @@ -1094,12 +1087,10 @@ resolve. This is useful for cases such as authoring a recursive schema that can be extended or a generic schema such as a list whose items are defined by the referencing schema. -The value of the `$dynamicRef` property MUST be formatted as a valid -[fragment-only IRI](#fragments).[^3] +The value of the `$dynamicRef` property MUST be a string and MUST conform to the +plain name fragment identifier syntax defined in {{fragments}}.[^3] -[^3]: `$dynamicAnchor` defines the anchor with plain text, e.g. `foo`. Although, -for historical reasons, the value of `$dynamicRef` still uses a fragment-only -IRI syntax, e.g. `#foo`. +[^3]: `$dynamicAnchor` and `$dynamicRef` form a string-matched pair. Resolution of `$dynamicRef` begins by identifying the outermost schema resource in the [dynamic scope](#scopes) which defines a matching `$dynamicAnchor`. The @@ -2323,7 +2314,7 @@ and only allows the "data" and "children" properties. An example instance with "children": { "type": "array", "items": { - "$dynamicRef": "#node" + "$dynamicRef": "node" } } } @@ -2348,50 +2339,34 @@ and only allows the "data" and "children" properties. An example instance with ``` 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 IRIs: - -- `https://example.com/tree#node` -- `https://example.com/strict-tree#node` - -In addition, JSON Schema implementations keep track of the fact that these -fragment identifiers were created with `$dynamicAnchor`. +present in each. 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 -`$dynamicRef`: to "#node" (note the `#` for IRI fragment syntax) in its `items` -subschema. That reference resolves to `https://example.com/tree#node`, which is -a IRI with a fragment created by `$dynamicAnchor`. Therefore we must examine the -dynamic scope before following the reference. +`$dynamicRef` to "node" in its `items` subschema. That reference resolves to +the `$dynamicAnchor` with value "node" in `https://example.com/tree`. Therefore +we must examine the dynamic scope before following the reference. At this point, the evaluation path is `/$ref/properties/children/items/$dynamicRef`, with a dynamic scope containing (from the outermost scope to the innermost): -1. `https://example.com/strict-tree#` -1. `https://example.com/tree#` -1. `https://example.com/tree#/properties/children` -1. `https://example.com/tree#/properties/children/items` - -Since we are looking for a plain name fragment identifier, which can be defined -anywhere within a schema resource, the JSON Pointer IRI fragments are irrelevant -to this check. That means that we can remove the fragments and eliminate -consecutive duplicates, producing: - 1. `https://example.com/strict-tree` -1. `https://example.com/tree` +2. `https://example.com/tree` -In this case, the outermost resource also has a "node" fragment identifier -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`. +To find the resolution target of the `$dynamicRef`, we start at the outermost +dynamic scope and traverse inward, stopping when we find a schema resource that +defines a matching `$dynamicAnchor`. -The reference in the "tree" schema resolves to the root of "strict-tree", so -"strict-tree" is applied not only to the tree instance's root, but also its -children. +In this case, the outermost resource "strict-tree" has a "node" identifier +defined by `$dynamicAnchor`. The subschema that defines that anchor is the +resolution target of the `$dynamicRef`. Therefore instead of resolving the +`$dynamicRef` to the `"$dynamicAnchor": "node"` in `https://example.com/tree`, +we resolve it to the `"$dynamicAnchor": "node"` in + `https://example.com/strict-tree`. This example shows both `$dynamicAnchor`s in the same place in each schema, -specifically the resource root schema. Since plain-name fragment identifiers are +specifically the resource root schema. Since plain-name identifiers 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 From ec18176e8a4a2d38a618d952793f4ca26ac11409 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Mon, 22 Dec 2025 09:35:09 +1300 Subject: [PATCH 2/2] update meta-schema usages for $dynamicRef --- specs/meta/README.md | 4 +-- specs/meta/meta.json | 36 ++++++++++++------------- specs/proposals/propertyDependencies.md | 2 +- specs/schema.json | 4 +-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/specs/meta/README.md b/specs/meta/README.md index 06b16500..dcd44fde 100644 --- a/specs/meta/README.md +++ b/specs/meta/README.md @@ -31,8 +31,8 @@ First, create a schema that just defines the new keywords. "$id": "https://example.com/schema/odds-evens", "properties": { - "odds": { "$dynamicRef": "#meta" }, - "evens": { "$dynamicRef": "#meta" } + "odds": { "$dynamicRef": "meta" }, + "evens": { "$dynamicRef": "meta" } } } ``` diff --git a/specs/meta/meta.json b/specs/meta/meta.json index 2c38fc06..d3d22b95 100644 --- a/specs/meta/meta.json +++ b/specs/meta/meta.json @@ -14,14 +14,14 @@ "$schema": { "$ref": "#/$defs/iriString" }, "$ref": { "$ref": "#/$defs/iriReferenceString" }, "$anchor": { "$ref": "#/$defs/anchorString" }, - "$dynamicRef": { "$ref": "#/$defs/iriReferenceString" }, + "$dynamicRef": { "$ref": "#/$defs/anchorString" }, "$dynamicAnchor": { "$ref": "#/$defs/anchorString" }, "$comment": { "type": "string" }, "$defs": { "type": "object", - "additionalProperties": { "$dynamicRef": "#meta" } + "additionalProperties": { "$dynamicRef": "meta" } }, "title": { "type": "string" @@ -47,40 +47,40 @@ "items": true }, "prefixItems": { "$ref": "#/$defs/schemaArray" }, - "items": { "$dynamicRef": "#meta" }, + "items": { "$dynamicRef": "meta" }, "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, "minContains": { "$ref": "#/$defs/nonNegativeInteger", "default": 1 }, - "contains": { "$dynamicRef": "#meta" }, - "additionalProperties": { "$dynamicRef": "#meta" }, + "contains": { "$dynamicRef": "meta" }, + "additionalProperties": { "$dynamicRef": "meta" }, "properties": { "type": "object", - "additionalProperties": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "meta" }, "default": {} }, "patternProperties": { "type": "object", - "additionalProperties": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "meta" }, "propertyNames": { "format": "regex" }, "default": {} }, "dependentSchemas": { "type": "object", - "additionalProperties": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "meta" }, "default": {} }, - "propertyNames": { "$dynamicRef": "#meta" }, - "if": { "$dynamicRef": "#meta" }, - "then": { "$dynamicRef": "#meta" }, - "else": { "$dynamicRef": "#meta" }, + "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": { "$dynamicRef": "#meta" }, - "unevaluatedItems": { "$dynamicRef": "#meta" }, - "unevaluatedProperties": { "$dynamicRef": "#meta" }, + "not": { "$dynamicRef": "meta" }, + "unevaluatedItems": { "$dynamicRef": "meta" }, + "unevaluatedProperties": { "$dynamicRef": "meta" }, "type": { "anyOf": [ { "$ref": "#/$defs/simpleTypes" }, @@ -137,7 +137,7 @@ "format": { "type": "string" }, "contentEncoding": { "type": "string" }, "contentMediaType": { "type": "string" }, - "contentSchema": { "$dynamicRef": "#meta" }, + "contentSchema": { "$dynamicRef": "meta" }, "$vocabulary": { "$comment": "Proposed keyword: https://github.com/json-schema-org/json-schema-spec/blob/main/specs/proposals/vocabularies.md" @@ -152,7 +152,7 @@ "propertyNames": { "pattern": "^[^$]|^\\$(id|schema|ref|anchor|dynamicRef|dynamicAnchor|comment|defs)$" }, - "$dynamicRef": "#extension", + "$dynamicRef": "extension", "unevaluatedProperties": false, "$defs": { "extension": { @@ -181,7 +181,7 @@ "schemaArray": { "type": "array", "minItems": 1, - "items": { "$dynamicRef": "#meta" } + "items": { "$dynamicRef": "meta" } }, "simpleTypes": { "enum": [ diff --git a/specs/proposals/propertyDependencies.md b/specs/proposals/propertyDependencies.md index 3fbf8ac7..11461842 100644 --- a/specs/proposals/propertyDependencies.md +++ b/specs/proposals/propertyDependencies.md @@ -121,7 +121,7 @@ vocabulary](../jsonschema-core.md#applicators). "additionalProperties": { "type": "object", "additionalProperties": { - "$dynamicRef": "#meta", + "$dynamicRef": "meta", "default": true }, "default": {} diff --git a/specs/schema.json b/specs/schema.json index 58da4e70..4d0451c3 100644 --- a/specs/schema.json +++ b/specs/schema.json @@ -28,7 +28,7 @@ "definitions": { "$comment": "\"definitions\" has been replaced by \"$defs\".", "type": "object", - "additionalProperties": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "meta" }, "deprecated": true, "default": {} }, @@ -37,7 +37,7 @@ "type": "object", "additionalProperties": { "anyOf": [ - { "$dynamicRef": "#meta" }, + { "$dynamicRef": "meta" }, { "$ref": "meta/validation#/$defs/stringArray" } ] },