Skip to content

Commit a2df8f9

Browse files
committed
Rework recursive reference section
This re-casts the behavior of "$recursiveAnchor" in terms of base URIs, and now this whole section is much easier to explain. The examples (or rather, an updated example) will be added in the next commit.
1 parent 828035b commit a2df8f9

File tree

1 file changed

+46
-177
lines changed

1 file changed

+46
-177
lines changed

jsonschema-core.xml

Lines changed: 46 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@
606606
due to the need to examine all subschemas for annotation collection, including
607607
those that cannot further change the assertion result.
608608
</t>
609-
<section title="Lexical Scope and Dynamic Scope">
609+
<section title="Lexical Scope and Dynamic Scope" anchor="scopes">
610610
<t>
611611
While most JSON Schema keywords can be evaluated on their own,
612612
or at most need to take into account the values or results of
@@ -1595,206 +1595,75 @@
15951595
</t>
15961596
</section>
15971597

1598-
<section title='Recursive References with "$recursiveRef" and "$recursiveAnchor"'>
1598+
<section title='Recursive References with "$recursiveRef" and "$recursiveAnchor"'
1599+
anchor="recursive-ref">
15991600
<t>
16001601
The "$recursiveRef" and "$recursiveAnchor" keywords are used to construct
16011602
extensible recursive schemas. A recursive schema is one that has
16021603
a reference to its own root, identified by the empty fragment
16031604
URI reference ("#").
16041605
</t>
1605-
<t>
1606-
Extending a recursive schema with "$ref" alone involves redefining all
1607-
recursive references in the source schema to point to the root of the
1608-
extension. This produces the correct recursive behavior in the extension,
1609-
which is that all recursion should reference the root of the extension.
1610-
</t>
1611-
<figure>
1612-
<preamble>
1613-
Consider the following two schemas. The first schema, identified
1614-
as "original" as it is the schema to be extended, describes
1615-
an object with one string property and one recursive reference
1616-
property, "r". The second schema, identified as "extension",
1617-
references the first, and describes an additional "things" property,
1618-
which is an array of recursive references.
1619-
It also repeats the description of "r" from the original schema.
1620-
</preamble>
1621-
<artwork>
1622-
<![CDATA[
1623-
{
1624-
"$schema": "https://json-schema.org/draft/2019-08/schema",
1625-
"$id": "https://example.com/original",
1626-
1627-
"properties": {
1628-
"name": {
1629-
"type": "string"
1630-
},
1631-
"r": {
1632-
"$ref": "#"
1633-
}
1634-
}
1635-
}
1636-
1637-
{
1638-
"$schema": "https://json-schema.org/draft/2019-08/schema",
1639-
"$id": "https://example.com/extension",
1640-
1641-
"$ref": "original",
1642-
"properties": {
1643-
"r": {
1644-
"$ref": "#"
1645-
},
1646-
"things": {
1647-
"type": "array"
1648-
"items": {
1649-
"$ref": "#"
1650-
}
1651-
}
1652-
}
1653-
}
1654-
]]>
1655-
</artwork>
1656-
<postamble>
1657-
This apparent duplication is important because
1658-
it resolves to "https://example.com/extension#", meaning that
1659-
for instance validated against the extension schema, the value
1660-
of "r" must be valid according to the extension, and not just the
1661-
original schema as "r" was described there.
1662-
</postamble>
1663-
</figure>
1664-
<t>
1665-
This approach is fine for a single recursive field, but the more
1666-
complicated the original schema, the more redefinitions are necessary
1667-
in the extension. This leads to a verbose and error-prone extension,
1668-
which must be kept synchronized with the original schema if the
1669-
original changes its recursive fields.
1670-
This approach can be seen in the meta-schema for JSON Hyper-Schema
1671-
in all prior drafts.
1672-
</t>
1673-
<section title='Enabling Recursion with "$recursiveAnchor"'>
1606+
<section title='Dynamically recursive references with "$recursiveRef"'>
1607+
<t>
1608+
The value of the "$recursiveRef" property MUST be a string which is
1609+
a URI-reference. It is a by-reference applicator that uses
1610+
a dynamically calculated base URI to resolve its value.
1611+
</t>
1612+
<t>
1613+
The behavior of this keyword is defined only for the value "#".
1614+
Implementations MAY choose to consider other values to be errors.
1615+
<cref>
1616+
This restriction may be relaxed in the future, but to date only
1617+
the value "#" has a clear use case.
1618+
</cref>
1619+
</t>
16741620
<t>
1675-
The desired behavior is for the recursive reference, "r", in the
1676-
original schema to resolve to the original schema when that
1677-
is the only schema being used, but to resolve to the extension
1678-
schema when using the extension. Then there would be no need
1679-
to redefine the "r" property, or others like it, in the extension.
1621+
The value of "$recursiveRef" is initially resolved against the
1622+
current base URI, in the same manner as for "$ref".
16801623
</t>
16811624
<t>
1682-
In order to create a recursive reference, we must do three things:
1683-
<list>
1684-
<t>
1685-
In our original schema, indicate that the schema author
1686-
intends for it to be extensible recursively.
1687-
</t>
1688-
<t>
1689-
In our extension schema, indicate that it is intended
1690-
to be a recursive extension.
1691-
</t>
1692-
<t>
1693-
Use a reference keyword that explicitly activates the
1694-
recursive behavior at the point of reference.
1695-
</t>
1696-
</list>
1697-
These three things together ensure that all schema authors
1698-
are intentionally constructing a recursive extension, which in
1699-
turn gives all uses of the regular "$ref" keyword confidence
1700-
that it only behaves as it appears to, using lexical scoping.
1625+
The schema identified by the resulting URI is examined for the
1626+
presence of "$recursiveAnchor", and a new base URI is calculated
1627+
as described for that keyword in the following section.
17011628
</t>
17021629
<t>
1703-
The "$recursiveAnchor" keyword is how schema authors indicate
1704-
that a schema can be extended recursively, and be a recursive
1705-
schema. This keyword MAY appear in the root schema of a
1706-
schema document, and MUST NOT appear in any subschema.
1630+
Finally, the value of "$recursiveRef" is resolved against the
1631+
new base URI determined according to "$recursiveAnchor" producing
1632+
the final resolved reference URI.
17071633
</t>
17081634
<t>
1709-
The value of "$recursiveAnchor" MUST be of type boolean, and
1710-
MUST be true. The value false is reserved for possible future use.
1635+
Note that in the absence of "$recursiveAnchor" (and in some cases
1636+
when it is present", "$recursiveRef"'s behavior is identical to
1637+
that of "$ref".
17111638
</t>
17121639
</section>
1713-
<section title='Dynamically recursive references with "$recursiveRef"'>
1640+
<section title='Enabling Recursion with "$recursiveAnchor"'>
17141641
<t>
1715-
The "$recursiveRef" keyword behaves identically to "$ref", except
1716-
that if the referenced schema has "$recursiveAnchor" set to true,
1717-
then the implementation MUST examine the dynamic scope for the
1718-
outermost (first seen) schema document with "$recursiveAnchor"
1719-
set to true. If such a schema document exists, then the target
1720-
of the "$recursiveRef" MUST be set to that document's URI, in
1721-
place of the URI produced by the rules for "$ref".
1642+
The value of the "$recursiveAnchor" property MUST be a boolean.
17221643
</t>
17231644
<t>
1724-
Note that if the schema referenced by "$recursiveRef" does not
1725-
contain "$recursiveAnchor" set to true, or if there are no other
1726-
"$recursiveAnchor" keywords set to true anywhere further back in
1727-
the dynamic scope, then "$recursiveRef"'s behavior is identical
1728-
to that of "$ref".
1645+
"$recursiveAnchor" is used to dynamically identify a base URI
1646+
at runtime for "$recursiveRef" by marking where such a calculation
1647+
can start, and where it stops. This keyword MUST NOT affect the
1648+
base URI of other keywords, unless they are explicitly defined
1649+
to rely on it.
1650+
</t>
1651+
<t>
1652+
If set to true, then when the containing schema object is used
1653+
as a dynamic reference target, a new base URI is determined
1654+
by examining the <xref target="scopes">dynamic scope</xref> for
1655+
the outermost schema that also contains "$recursiveAnchor"
1656+
with a value of true. The base URI of that schema is then used
1657+
as the dynamic base URI.
17291658
</t>
1730-
<figure>
1731-
<preamble>
1732-
With this in mind, we can rewrite the previous example:
1733-
</preamble>
1734-
<artwork>
1735-
<![CDATA[
1736-
{
1737-
"$schema": "https://json-schema.org/draft/2019-08/schema",
1738-
"$id": "https://example.com/original",
1739-
"$recursiveAnchor": true,
1740-
1741-
"properties": {
1742-
"name": {
1743-
"type": "string"
1744-
},
1745-
"r": {
1746-
"$recursiveRef": "#"
1747-
}
1748-
}
1749-
}
1750-
1751-
{
1752-
"$schema": "https://json-schema.org/draft/2019-08/schema",
1753-
"$id": "https://example.com/extension",
1754-
"$recursiveAnchor": true,
1755-
1756-
"$ref": "original",
1757-
"properties": {
1758-
"things": {
1759-
"type": "array"
1760-
"items": {
1761-
"$recursiveRef": "#"
1762-
}
1763-
}
1764-
}
1765-
}
1766-
]]>
1767-
</artwork>
1768-
<postamble>
1769-
Note that the "r" property no longer appears in the
1770-
extension schema. Instead, all "$ref"s have been changed
1771-
to "$recursiveRef"s, and both schemas have "$recursiveAnchor"
1772-
set to true in their root schema.
1773-
</postamble>
1774-
</figure>
17751659
<t>
1776-
When using the original schema on its own, there is no change
1777-
in behavior. The "$recursiveRef" does lead to a schema where
1778-
"$recursiveAnchor" is set to true, but since the original schema
1779-
is the only schema document in the dynamics scope (it references
1780-
itself, and does not reference any other schema documents), the
1781-
behavior is effectively the same as "$ref".
1660+
If no such schema exists, then the base URI is unchanged.
17821661
</t>
17831662
<t>
1784-
When using the extension schema, the "$recursiveRef" within
1785-
that schema (for the array items within "things") also effectively
1786-
behaves like "$ref". The extension schema is the outermost
1787-
dynamic scope, so the reference target is not changed.
1663+
If this keyword is set to false, the base URI is unchanged.
17881664
</t>
17891665
<t>
1790-
In contrast, when using the extension schema, the "$recursiveRef"
1791-
for "r" in the original schema now behaves differently. Its
1792-
initial target is the root schema of the original schema document,
1793-
which has "$recursiveAnchor" set to true. In this case, the
1794-
outermost dynamic scope that also has "$recursiveAnchor" set to
1795-
true is the extension schema. So when using the extensions schema,
1796-
"r"'s reference in the original schema will resolve to
1797-
"https://example.com/extension#", not "https://example.com/original#".
1666+
Omitting this keyword has the same behavior as a value of false.
17981667
</t>
17991668
</section>
18001669
</section>

0 commit comments

Comments
 (0)