Skip to content

Commit 29fdf06

Browse files
authored
Merge pull request #654 from handrews/rec-ref-anchor
$recursiveRef part 2 of 3: $recursiveRef and $recursiveAnchor
2 parents 4f9d0ba + 062b037 commit 29fdf06

File tree

1 file changed

+249
-32
lines changed

1 file changed

+249
-32
lines changed

jsonschema-core.xml

Lines changed: 249 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,11 @@
205205
The schemas to be applied may be present as subschemas comprising all or
206206
part of the keyword's value. Alternatively, an applicator may refer to
207207
a schema elsewhere in the same schema document, or in a different one.
208-
The mechanism for identifying such referred schemas is defined by the
208+
The mechanism for identifying such referenced schemas is defined by the
209209
keyword.
210210
</t>
211211
<t>
212-
Applicator keywords also define how subschema or referred schema
212+
Applicator keywords also define how subschema or referenced schema
213213
boolean <xref target="assertions">assertion</xref>
214214
results are modified and/or combined to produce the boolean result
215215
of the applicator. Applicators may apply any boolean logic operation
@@ -626,20 +626,19 @@
626626
and used with caution when defining additional keywords.
627627
</t>
628628
</section>
629-
<section title="Referred and Referring Schemas" anchor="referred">
629+
<section title="Referenced and Referencing Schemas" anchor="referenced">
630630
<t>
631631
As noted in <xref target="applicators" />, an applicator keyword may
632632
refer to a schema to be applied, rather than including it as a
633633
subschema in the applicator's value. In such situations, the
634-
schema being applied is known as the referred (or referenced) schema,
635-
while the schema containing the applicator keyword is the referring
636-
(or referencing) schema.
634+
schema being applied is known as the referenced schema, while
635+
the schema containing the applicator keyword is the referencing schema.
637636
</t>
638637
<t>
639638
While root schemas and subschemas are static concepts based on a
640-
schema's position within a schema document, referred and referring
639+
schema's position within a schema document, referenced and referencing
641640
schemas are dynamic. Different pairs of schemas may find themselves
642-
in various referred and referring arrangements during the evaluation
641+
in various referenced and referencing arrangements during the evaluation
643642
of an instance against a schema.
644643
</t>
645644
<t>
@@ -1006,35 +1005,253 @@
10061005
</section>
10071006
</section>
10081007

1009-
<section title='Schema References With "$ref"' anchor="ref">
1008+
<section title="Schema References">
10101009
<t>
1011-
The "$ref" keyword can be used to reference a schema which is to be applied to the
1012-
current instance location. "$ref" is an applicator key word, applying the referred
1013-
schema to the instance.
1010+
Several keywords can be used to reference a schema which is to be applied to the
1011+
current instance location. "$ref" and "$recursiveRef" are an applicator
1012+
keywords, applying the referenced schema to the instance. "$recursiveAnchor"
1013+
is a helper keyword that controls how the referenced schema of "$recursiveRef"
1014+
is determined.
10141015
</t>
10151016
<t>
1016-
The value of the "$ref" property MUST be a string which is a URI Reference.
1017-
Resolved against the current URI base, it identifies the URI of a schema to use.
1017+
As the value of "$ref" and "$recursiveRef" are URI References, this allows
1018+
the possibility to externalise or divide a schema across multiple files,
1019+
and provides the ability to validate recursive structures through
1020+
self-reference.
10181021
</t>
10191022
<t>
1020-
As the value of "$ref" is a URI Reference, this allows the possibility to externalise or
1021-
divide a schema across multiple files, and provides the ability to validate recursive structures
1022-
through self-reference.
1023-
</t>
1024-
<t>
1025-
The URI is not a network locator, only an identifier. A schema need not be
1026-
downloadable from the address if it is a network-addressable URL, and
1027-
implementations SHOULD NOT assume they should perform a network operation when they
1028-
encounter a network-addressable URI.
1029-
</t>
1030-
<t>
1031-
A schema MUST NOT be run into an infinite loop against a schema. For example, if two
1032-
schemas "#alice" and "#bob" both have an "allOf" property that refers to the other,
1033-
a naive validator might get stuck in an infinite recursive loop trying to validate
1034-
the instance.
1035-
Schemas SHOULD NOT make use of infinite recursive nesting like this; the behavior is
1036-
undefined.
1023+
The resolved URI produced by these keywords is not necessarily a network
1024+
locator, only an identifier. A schema need not be downloadable from the
1025+
address if it is a network-addressable URL, and implementations SHOULD NOT
1026+
assume they should perform a network operation when they encounter
1027+
a network-addressable URI.
10371028
</t>
1029+
1030+
<section title='Direct References with "$ref"' anchor="ref">
1031+
<t>
1032+
The "$ref" keyword is used to reference a statically identified schema.
1033+
</t>
1034+
<t>
1035+
The value of the "$ref" property MUST be a string which is a URI Reference.
1036+
Resolved against the current URI base, it identifies the URI of a schema
1037+
to use.
1038+
</t>
1039+
</section>
1040+
1041+
<section title='Recursive References with "$recursiveRef" and "$recursiveAnchor"'>
1042+
<t>
1043+
The "$recursiveRef" and "$recursiveAnchor" keywords are used to construct
1044+
extensible recursive schemas. A recursive schema is one that has
1045+
a reference to its own root, identified by the empty fragment
1046+
URI reference ("#").
1047+
</t>
1048+
<t>
1049+
Extending a recursive schema with "$ref" alone involves redefining all
1050+
recursive references in the source schema to point to the root of the
1051+
extension. This produces the correct recursive behavior in the extension,
1052+
which is that all recursion should reference the root of the extension.
1053+
</t>
1054+
<figure>
1055+
<preamble>
1056+
Consider the following two schemas. The first schema, identified
1057+
as "original" as it is the schema to be extended, describes
1058+
an object with one string property and one recursive reference
1059+
property, "r". The second schema, identified as "extension",
1060+
references the first, and describes an additional things" property,
1061+
which is an array of recursive references.
1062+
It also repeats the description of "r" from the original schema.
1063+
</preamble>
1064+
<artwork>
1065+
<![CDATA[
1066+
{
1067+
"$schema": "http://json-schema.org/draft-08/schema#",
1068+
"$id": "https://example.com/original",
1069+
1070+
"properties": {
1071+
"name": {
1072+
"type": "string"
1073+
},
1074+
"r": {
1075+
"$ref": "#"
1076+
}
1077+
}
1078+
}
1079+
1080+
{
1081+
"$schema": "http://json-schema.org/draft-08/schema#",
1082+
"$id": "https://example.com/extension",
1083+
1084+
"$ref": "original",
1085+
"properties": {
1086+
"r": {
1087+
"$ref": "#"
1088+
},
1089+
"things": {
1090+
"type": "array"
1091+
"items": {
1092+
"$ref": "#"
1093+
}
1094+
}
1095+
}
1096+
}
1097+
]]>
1098+
</artwork>
1099+
<postamble>
1100+
This apparent duplication is important because
1101+
it resolves to "https://example.com/extension#", meaning that
1102+
for instance validated against the extension schema, the value
1103+
of "r" must be valid according to the extension, and not just the
1104+
original schema as "r" was described there.
1105+
</postamble>
1106+
</figure>
1107+
<t>
1108+
This approach is fine for a single recursive field, but the more
1109+
complicated the original schema, the more redefinitions are necessary
1110+
in the extension. This leads to a verbose and error-prone extension,
1111+
which must be kept synchronized with the original schema if the
1112+
original changes its recursive fields.
1113+
This approach can be seen in the meta-schema for JSON Hyper-Schema
1114+
in all prior drafts.
1115+
</t>
1116+
<section title='Enabling Recursion with "$recursiveAnchor"'>
1117+
<t>
1118+
The desired behavior is for the recursive reference, "r", in the
1119+
original schema to resolve to the original schema when that
1120+
is the only schema being used, but to resolve to the extension
1121+
schema when using the extension. Then there would be no need
1122+
to redefine the "r" property, or others like it, in the extension.
1123+
</t>
1124+
<t>
1125+
In order to create a recursive reference, we must do three things:
1126+
<list>
1127+
<t>
1128+
In our original schema, indicate that the schema author
1129+
intends for it to be extensible recursively.
1130+
</t>
1131+
<t>
1132+
In our extension schema, indicate that it is intended
1133+
to be a recursive extension.
1134+
</t>
1135+
<t>
1136+
Use a reference keyword that explicitly activates the
1137+
recursive behavior at the point of reference.
1138+
</t>
1139+
</list>
1140+
These three things together ensure that all schema authors
1141+
are intentionally constructing a recursive extension, which in
1142+
turn gives all uses of the regular "$ref" keyword confidence
1143+
that it only behaves as it appears to, using lexical scoping.
1144+
</t>
1145+
<t>
1146+
The "$recursiveAnchor" keyword is how schema authors indicate
1147+
that a schema can be extended recursively, and be a recursive
1148+
schema. This keyword MAY appear in the root schema of a
1149+
schema document, and MUST NOT appear in any subschema.
1150+
</t>
1151+
<t>
1152+
The value of "$recursiveAnchor" MUST be of type boolean, and
1153+
MUST be true. The value false is reserved for possible future use.
1154+
</t>
1155+
</section>
1156+
<section title='Dynamically recursive references with "$recursiveRef"'>
1157+
<t>
1158+
The "$recursiveRef" keyword behaves identically to "$ref", except
1159+
that if the referenced schema has "$recursiveAnchor" set to true,
1160+
then the implementation MUST examine the dynamic scope for the
1161+
outermost (first seen) schema document with "$recursiveAnchor"
1162+
set to true. If such a schema document exists, then the target
1163+
of the "$recursiveRef" MUST be set to that document's URI, in
1164+
place of the URI produced by the rules for "$ref".
1165+
</t>
1166+
<t>
1167+
Note that if the schema referenced by "$recursiveRef" does not
1168+
contain "$recursiveAnchor" set to true, or if there are no other
1169+
"$recursiveAnchor" keywords set to true anywhere further back in
1170+
the dynamic scope, then "$recursiveRef"'s behavior is identical
1171+
to that of "$ref".
1172+
</t>
1173+
<figure>
1174+
<preamble>
1175+
With this in mind, we can rewrite the previous example:
1176+
</preamble>
1177+
<artwork>
1178+
<![CDATA[
1179+
{
1180+
"$schema": "http://json-schema.org/draft-08/schema#",
1181+
"$id": "https://example.com/original",
1182+
"$recursiveAnchor": true,
1183+
1184+
"properties": {
1185+
"name": {
1186+
"type": "string"
1187+
},
1188+
"r": {
1189+
"$recursiveRef": "#"
1190+
}
1191+
}
1192+
}
1193+
1194+
{
1195+
"$schema": "http://json-schema.org/draft-08/schema#",
1196+
"$id": "https://example.com/extension",
1197+
"$recursiveAnchor": true,
1198+
1199+
"$ref": "original",
1200+
"properties": {
1201+
"things": {
1202+
"type": "array"
1203+
"items": {
1204+
"$recursiveRef": "#"
1205+
}
1206+
}
1207+
}
1208+
}
1209+
]]>
1210+
</artwork>
1211+
<postamble>
1212+
Note that the "r" property no longer appears in the
1213+
extension schema. Instead, all "$ref"s have been changed
1214+
to "$recursiveRef"s, and both schemas have "$recursiveAnchor"
1215+
set to true in their root schema.
1216+
</postamble>
1217+
</figure>
1218+
<t>
1219+
When using the original schema on its own, there is no change
1220+
in behavior. The "$recursiveRef" does lead to a schema where
1221+
"$recursiveAnchor" is set to true, but since the original schema
1222+
is the only schema document in the dynamics scope (it references
1223+
itself, and does not reference any other schema documents), the
1224+
behavior is effectively the same as "$ref".
1225+
</t>
1226+
<t>
1227+
When using the extension schema, the "$recursiveRef" within
1228+
that schema (for the array items within "things") also effectively
1229+
behaves like "$ref". The extension schema is the outermost
1230+
dynamic scope, so the reference target is not changed.
1231+
</t>
1232+
<t>
1233+
In contrast, when using the extension schema, the "$recursiveRef"
1234+
for "r" in the original schema now behaves differently. Its
1235+
initial target is the root schema of the original schema document,
1236+
which has "$recursiveAnchor" set to true. In this case, the
1237+
outermost dynamic scope that also has "$recursiveAnchor" set to
1238+
true is the extension schema. So when using the extensions schema,
1239+
"r"'s reference in the original schema will resolve to
1240+
"https://example.com/extension#", not "https://example.com/original#".
1241+
</t>
1242+
</section>
1243+
</section>
1244+
1245+
<section title="Guarding Against Inifinite Recursion">
1246+
<t>
1247+
A schema MUST NOT be run into an infinite loop against an instance. For
1248+
example, if two schemas "#alice" and "#bob" both have an "allOf" property
1249+
that refers to the other, a naive validator might get stuck in an infinite
1250+
recursive loop trying to validate the instance. Schemas SHOULD NOT make
1251+
use of infinite recursive nesting like this; the behavior is undefined.
1252+
</t>
1253+
</section>
1254+
10381255
<section title="Loading a referenced schema">
10391256
<t>
10401257
The use of URIs to identify remote schemas does not necessarily mean anything is downloaded,
@@ -1313,7 +1530,7 @@
13131530
The application can use the schema location path to determine which
13141531
values are which. The values in the feature's immediate "enabled"
13151532
property schema are more specific, while the values under the re-usable
1316-
schema that is referred to with "$ref" are more generic. The schema
1533+
schema that is referenced to with "$ref" are more generic. The schema
13171534
location path will show whether each value was found by crossing a
13181535
"$ref" or not.
13191536
</t>

0 commit comments

Comments
 (0)