Skip to content

Commit 98eb591

Browse files
authored
Merge pull request #1656 from json-schema-org/gregsdennis/dynamic-ref-naming
Gregsdennis/dynamic ref naming
2 parents d7027fb + 92249ec commit 98eb591

5 files changed

Lines changed: 71 additions & 83 deletions

File tree

specs/jsonschema-core.md

Lines changed: 48 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,32 +1010,21 @@ fragments to identify subschemas is sometimes preferable because it is not tied
10101010
to a particular structural location. This allows a subschema to be relocated
10111011
without requiring references to be updated.
10121012

1013-
The `$anchor` and `$dynamicAnchor` keywords are used to define
1014-
location-independent identifiers for subschemas within a schema resource.
1015-
1016-
`$anchor` defines a plain name fragment identifier that can be used in IRI
1017-
fragments as an alternative to JSON Pointers.[^6] See {{fragments}}.
1013+
The `$anchor` keyword is used to define location-independent identifiers for
1014+
subschemas within a schema resource. `$anchor` defines a plain name fragment
1015+
identifier that can be used in IRI fragments as an alternative to JSON
1016+
Pointers.[^4] See {{fragments}}.
10181017

10191018
[^6]: Note that the anchor string does not include the "#" character, as it is
10201019
just a fragment identifier not an IRI reference. To reference the "foo"
10211020
`$anchor` from the same schema resource, you would use the fragment-only IRI
10221021
`#foo`. See below for full examples.
10231022

1024-
`$dynamicAnchor` defines a different kind of fragment identifier that only has
1025-
meaning when used with `$dynamicRef`. It's not a normal fragment identifier and
1026-
therefore can't be used anywhere other than `$dynamicRef`. Normal [fragment
1027-
identifiers](https://www.rfc-editor.org/rfc/rfc3986#section-3.5) identify the
1028-
secondary resource (the subschema) while the rest of the IRI identifies the
1029-
primary resource (the schema resource). The fragment identifiers defined by
1030-
`$dynamicAnchor` are not normal fragment identifies because they identify both
1031-
the primary resource and the secondary resource. See {{dynamic-ref}} for
1032-
details.
1033-
1034-
If present, the value of these keywords MUST be a string and MUST conform to the
1023+
If present, the value of this keyword MUST be a string and MUST conform to the
10351024
plain name fragment identifier syntax defined in {{fragments}}.
10361025

1037-
`$anchor`, `$dynamicAnchor`, and any extensions that define a plain name
1038-
fragment identifiers MUST match XML's [`NCName`
1026+
`$anchor`, and any extensions that define a plain name fragment identifier MUST
1027+
match XML's [`NCName`
10391028
production](https://www.w3.org/TR/2006/REC-xml-names11-20060816/#NT-NCName). For
10401029
convenience, the `NCName` syntax is reproduced here in ABNF form, using a
10411030
minimal set of rules:
@@ -1053,13 +1042,25 @@ NCNameChar = NCNameStartChar / "-" / "." / DIGIT
10531042
/ %xB7 / %x0300-036F / %x203F-2040
10541043
```
10551044

1045+
#### Defining dynamically scoped identifiers {#dynamic-anchors}
1046+
1047+
The `$dynamicAnchor` keyword is used to define location-independent identifiers
1048+
for subschemas within the dynamic scope of a schema evaluation. They are only
1049+
used by `$dynamicRef`. They are not meaningful in IRI fragments.
1050+
1051+
The fragment identifiers defined by `$dynamicAnchor` are not normal fragment
1052+
identifiers because they identify both the primary resource and the secondary
1053+
resource. See {{dynamic-ref}} for details.
1054+
1055+
If present, the value of this keyword MUST be a string.
1056+
10561057
#### Duplicate schema identifiers {#duplicate-iris}
10571058

10581059
A schema MAY (and likely will) have multiple IRIs, but there is no way for an
10591060
IRI to identify more than one schema. When multiple schemas attempt to identify
1060-
as the same IRI through the use of `$id`, `$anchor`, `$dynamicAnchor`, or any
1061-
other mechanism, implementations SHOULD raise an error condition. Otherwise the
1062-
result is undefined, and even if documented will not be interoperable.
1061+
as the same IRI through the use of `$id`, `$anchor`, or any other mechanism,
1062+
implementations SHOULD raise an error condition. Otherwise the result is
1063+
undefined, and even if documented will not be interoperable.
10631064

10641065
#### Schema References {#references}
10651066

@@ -1093,17 +1094,20 @@ resolve. This is useful for cases such as authoring a recursive schema that can
10931094
be extended or a generic schema such as a list whose items are defined by the
10941095
referencing schema.
10951096

1096-
The value of the `$dynamicRef` property MUST be formatted as a valid
1097-
[fragment-only IRI](#fragments).[^8]
1097+
The value of the `$dynamicRef` property MUST be a string and MUST conform to the
1098+
plain name fragment identifier syntax defined in {{fragments}}.[^3]
10981099

1099-
[^8]: `$dynamicAnchor` defines the anchor with plain text, e.g. `foo`. Although,
1100-
for historical reasons, the value of `$dynamicRef` still uses a fragment-only
1101-
IRI syntax, e.g. `#foo`.
1100+
[^3]: The reason for limiting `$dynamicAnchor` to fragment identifier syntax no
1101+
longer exists because it isn't used in URIs, but the restriction remains to
1102+
avoid changing things that don't have a compelling reason to change.
1103+
1104+
`$dynamicAnchor` and `$dynamicRef` form a string-matched pair.
11021105

11031106
Resolution of `$dynamicRef` begins by identifying the outermost schema resource
11041107
in the [dynamic scope](#scopes) which defines a matching `$dynamicAnchor`. The
1105-
schema to apply is the subschema of this resource which contains the matching
1106-
`$dynamicAnchor`. If no matching `$dynamicAnchor` is found, see {{failed-refs}}.
1108+
schema to apply is the subschema of this resource which contains a
1109+
`$dynamicAnchor` matching the value of `$dynamicRef`. If no matching
1110+
`$dynamicAnchor` is found, see {{failed-refs}}.
11071111

11081112
For a full example using these keywords, see {{dynamic-example}}.[^9]
11091113

@@ -2322,7 +2326,7 @@ and only allows the "data" and "children" properties. An example instance with
23222326
"children": {
23232327
"type": "array",
23242328
"items": {
2325-
"$dynamicRef": "#node"
2329+
"$dynamicRef": "node"
23262330
}
23272331
}
23282332
}
@@ -2347,50 +2351,34 @@ and only allows the "data" and "children" properties. An example instance with
23472351
```
23482352

23492353
When we load these two schemas, we will notice the `$dynamicAnchor` named "node"
2350-
(note the lack of "#" as this is just the name) present in each, resulting in
2351-
the following full schema IRIs:
2352-
2353-
- `https://example.com/tree#node`
2354-
- `https://example.com/strict-tree#node`
2355-
2356-
In addition, JSON Schema implementations keep track of the fact that these
2357-
fragment identifiers were created with `$dynamicAnchor`.
2354+
present in each.
23582355

23592356
If we apply the "strict-tree" schema to the instance, we will follow the `$ref`
23602357
to the "tree" schema, examine its "children" subschema, and find the
2361-
`$dynamicRef`: to "#node" (note the `#` for IRI fragment syntax) in its `items`
2362-
subschema. That reference resolves to `https://example.com/tree#node`, which is
2363-
a IRI with a fragment created by `$dynamicAnchor`. Therefore we must examine the
2364-
dynamic scope before following the reference.
2358+
`$dynamicRef` to "node" in its `items` subschema. That reference resolves to
2359+
the `$dynamicAnchor` with value "node" in `https://example.com/tree`. Therefore
2360+
we must examine the dynamic scope before following the reference.
23652361

23662362
At this point, the evaluation path is
23672363
`/$ref/properties/children/items/$dynamicRef`, with a dynamic scope containing
23682364
(from the outermost scope to the innermost):
23692365

2370-
1. `https://example.com/strict-tree#`
2371-
1. `https://example.com/tree#`
2372-
1. `https://example.com/tree#/properties/children`
2373-
1. `https://example.com/tree#/properties/children/items`
2374-
2375-
Since we are looking for a plain name fragment identifier, which can be defined
2376-
anywhere within a schema resource, the JSON Pointer IRI fragments are irrelevant
2377-
to this check. That means that we can remove the fragments and eliminate
2378-
consecutive duplicates, producing:
2379-
23802366
1. `https://example.com/strict-tree`
2381-
1. `https://example.com/tree`
2367+
2. `https://example.com/tree`
23822368

2383-
In this case, the outermost resource also has a "node" fragment identifier
2384-
defined by `$dynamicAnchor`. Therefore instead of resolving the `$dynamicRef` to
2385-
`https://example.com/tree#node`, we resolve it to
2386-
`https://example.com/strict-tree#node`.
2369+
To find the resolution target of the `$dynamicRef`, we start at the outermost
2370+
dynamic scope and traverse inward, stopping when we find a schema resource that
2371+
defines a matching `$dynamicAnchor`.
23872372

2388-
The reference in the "tree" schema resolves to the root of "strict-tree", so
2389-
"strict-tree" is applied not only to the tree instance's root, but also its
2390-
children.
2373+
In this case, the outermost resource "strict-tree" has a "node" identifier
2374+
defined by `$dynamicAnchor`. The subschema that defines that anchor is the
2375+
resolution target of the `$dynamicRef`. Therefore instead of resolving the
2376+
`$dynamicRef` to the `"$dynamicAnchor": "node"` in `https://example.com/tree`,
2377+
we resolve it to the `"$dynamicAnchor": "node"` in
2378+
`https://example.com/strict-tree`.
23912379

23922380
This example shows both `$dynamicAnchor`s in the same place in each schema,
2393-
specifically the resource root schema. Since plain-name fragment identifiers are
2381+
specifically the resource root schema. Since dynamically scoped identifiers are
23942382
independent of the JSON structure, this would work just as well if one or both
23952383
of the node schema objects were moved under `$defs`. It is the matching
23962384
`$dynamicAnchor` values which tell us how to resolve the dynamic reference, not

specs/meta/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ First, create a schema that just defines the new keywords.
3131
"$id": "https://example.com/schema/odds-evens",
3232

3333
"properties": {
34-
"odds": { "$dynamicRef": "#meta" },
35-
"evens": { "$dynamicRef": "#meta" }
34+
"odds": { "$dynamicRef": "meta" },
35+
"evens": { "$dynamicRef": "meta" }
3636
}
3737
}
3838
```

specs/meta/meta.json

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
"$schema": { "$ref": "#/$defs/iriString" },
1515
"$ref": { "$ref": "#/$defs/iriReferenceString" },
1616
"$anchor": { "$ref": "#/$defs/anchorString" },
17-
"$dynamicRef": { "$ref": "#/$defs/iriReferenceString" },
17+
"$dynamicRef": { "$ref": "#/$defs/anchorString" },
1818
"$dynamicAnchor": { "$ref": "#/$defs/anchorString" },
1919
"$comment": {
2020
"type": "string"
2121
},
2222
"$defs": {
2323
"type": "object",
24-
"additionalProperties": { "$dynamicRef": "#meta" }
24+
"additionalProperties": { "$dynamicRef": "meta" }
2525
},
2626
"title": {
2727
"type": "string"
@@ -47,40 +47,40 @@
4747
"items": true
4848
},
4949
"prefixItems": { "$ref": "#/$defs/schemaArray" },
50-
"items": { "$dynamicRef": "#meta" },
50+
"items": { "$dynamicRef": "meta" },
5151
"maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
5252
"minContains": {
5353
"$ref": "#/$defs/nonNegativeInteger",
5454
"default": 1
5555
},
56-
"contains": { "$dynamicRef": "#meta" },
57-
"additionalProperties": { "$dynamicRef": "#meta" },
56+
"contains": { "$dynamicRef": "meta" },
57+
"additionalProperties": { "$dynamicRef": "meta" },
5858
"properties": {
5959
"type": "object",
60-
"additionalProperties": { "$dynamicRef": "#meta" },
60+
"additionalProperties": { "$dynamicRef": "meta" },
6161
"default": {}
6262
},
6363
"patternProperties": {
6464
"type": "object",
65-
"additionalProperties": { "$dynamicRef": "#meta" },
65+
"additionalProperties": { "$dynamicRef": "meta" },
6666
"propertyNames": { "format": "regex" },
6767
"default": {}
6868
},
6969
"dependentSchemas": {
7070
"type": "object",
71-
"additionalProperties": { "$dynamicRef": "#meta" },
71+
"additionalProperties": { "$dynamicRef": "meta" },
7272
"default": {}
7373
},
74-
"propertyNames": { "$dynamicRef": "#meta" },
75-
"if": { "$dynamicRef": "#meta" },
76-
"then": { "$dynamicRef": "#meta" },
77-
"else": { "$dynamicRef": "#meta" },
74+
"propertyNames": { "$dynamicRef": "meta" },
75+
"if": { "$dynamicRef": "meta" },
76+
"then": { "$dynamicRef": "meta" },
77+
"else": { "$dynamicRef": "meta" },
7878
"allOf": { "$ref": "#/$defs/schemaArray" },
7979
"anyOf": { "$ref": "#/$defs/schemaArray" },
8080
"oneOf": { "$ref": "#/$defs/schemaArray" },
81-
"not": { "$dynamicRef": "#meta" },
82-
"unevaluatedItems": { "$dynamicRef": "#meta" },
83-
"unevaluatedProperties": { "$dynamicRef": "#meta" },
81+
"not": { "$dynamicRef": "meta" },
82+
"unevaluatedItems": { "$dynamicRef": "meta" },
83+
"unevaluatedProperties": { "$dynamicRef": "meta" },
8484
"type": {
8585
"anyOf": [
8686
{ "$ref": "#/$defs/simpleTypes" },
@@ -137,7 +137,7 @@
137137
"format": { "type": "string" },
138138
"contentEncoding": { "type": "string" },
139139
"contentMediaType": { "type": "string" },
140-
"contentSchema": { "$dynamicRef": "#meta" },
140+
"contentSchema": { "$dynamicRef": "meta" },
141141

142142
"$vocabulary": {
143143
"$comment": "Proposed keyword: https://github.com/json-schema-org/json-schema-spec/blob/main/specs/proposals/vocabularies.md"
@@ -152,7 +152,7 @@
152152
"propertyNames": {
153153
"pattern": "^[^$]|^\\$(id|schema|ref|anchor|dynamicRef|dynamicAnchor|comment|defs)$"
154154
},
155-
"$dynamicRef": "#extension",
155+
"$dynamicRef": "extension",
156156
"unevaluatedProperties": false,
157157
"$defs": {
158158
"extension": {
@@ -181,7 +181,7 @@
181181
"schemaArray": {
182182
"type": "array",
183183
"minItems": 1,
184-
"items": { "$dynamicRef": "#meta" }
184+
"items": { "$dynamicRef": "meta" }
185185
},
186186
"simpleTypes": {
187187
"enum": [

specs/proposals/propertyDependencies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ vocabulary](../jsonschema-core.md#applicators).
121121
"additionalProperties": {
122122
"type": "object",
123123
"additionalProperties": {
124-
"$dynamicRef": "#meta",
124+
"$dynamicRef": "meta",
125125
"default": true
126126
},
127127
"default": {}

specs/schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"definitions": {
2929
"$comment": "\"definitions\" has been replaced by \"$defs\".",
3030
"type": "object",
31-
"additionalProperties": { "$dynamicRef": "#meta" },
31+
"additionalProperties": { "$dynamicRef": "meta" },
3232
"deprecated": true,
3333
"default": {}
3434
},
@@ -37,7 +37,7 @@
3737
"type": "object",
3838
"additionalProperties": {
3939
"anyOf": [
40-
{ "$dynamicRef": "#meta" },
40+
{ "$dynamicRef": "meta" },
4141
{ "$ref": "meta/validation#/$defs/stringArray" }
4242
]
4343
},

0 commit comments

Comments
 (0)