Skip to content

Commit fadd36d

Browse files
Address review feedback for recursive/dynamic ref support (#2985)
* Support $recursiveRef/$dynamicRef in JSON Schema and OpenAPI * docs: update llms.txt files Generated by GitHub Actions * Remove unrelated diff in test_schema_version.py * Fix partial branch coverage * Address review feedback and merge main * docs: update llms.txt files Generated by GitHub Actions --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 4be7699 commit fadd36d

File tree

3 files changed

+3
-15
lines changed

3 files changed

+3
-15
lines changed

docs/llms-full.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24345,8 +24345,6 @@ The following features are tracked in the codebase with their implementation sta
2434524345
| Feature | Introduced | Status | Notes |
2434624346
|---------|------------|--------|-------|
2434724347
| `$anchor` | 2019-09 | ❌ Not supported | Use `$ref` with `$id` instead |
24348-
| `$recursiveRef` / `$recursiveAnchor` | 2019-09 | ✅ Supported | Statically resolved to self-reference |
24349-
| `$dynamicRef` / `$dynamicAnchor` | 2020-12 | ✅ Supported | Statically resolved to self-reference |
2435024348
| `unevaluatedProperties` | 2019-09 | ❌ Not supported | Use `additionalProperties` instead |
2435124349
| `unevaluatedItems` | 2019-09 | ❌ Not supported | Use `additionalItems` instead |
2435224350
| `contentMediaType` | Draft 7 | ❌ Not supported | Content type hints ignored |

docs/supported_formats.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,6 @@ The following features are tracked in the codebase with their implementation sta
177177
| Feature | Introduced | Status | Notes |
178178
|---------|------------|--------|-------|
179179
| `$anchor` | 2019-09 | ❌ Not supported | Use `$ref` with `$id` instead |
180-
| `$recursiveRef` / `$recursiveAnchor` | 2019-09 | ✅ Supported | Statically resolved to self-reference |
181-
| `$dynamicRef` / `$dynamicAnchor` | 2020-12 | ✅ Supported | Statically resolved to self-reference |
182180
| `unevaluatedProperties` | 2019-09 | ❌ Not supported | Use `additionalProperties` instead |
183181
| `unevaluatedItems` | 2019-09 | ❌ Not supported | Use `additionalItems` instead |
184182
| `contentMediaType` | Draft 7 | ❌ Not supported | Content type hints ignored |

src/datamodel_code_generator/parser/jsonschema.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,15 +1576,16 @@ def _build_anchor_indexes(self, obj: JsonSchemaObject, path: list[str]) -> None:
15761576
root_len = len(root_key)
15771577
if root_len < len(path):
15781578
suffix_parts = path[root_len:]
1579-
# Strip leading '#' from fragment markers (e.g. '#/$defs' -> '$defs')
15801579
first = suffix_parts[0]
15811580
if first.startswith("#"):
15821581
suffix_parts = [first[1:].lstrip("/"), *suffix_parts[1:]]
15831582
ref_path = "#/" + "/".join(suffix_parts)
15841583
else:
15851584
ref_path = "#"
15861585
if obj.recursiveAnchor:
1587-
self._recursive_anchor_index.setdefault(root_key, []).append(ref_path)
1586+
anchors = self._recursive_anchor_index.setdefault(root_key, [])
1587+
if ref_path not in anchors:
1588+
anchors.append(ref_path)
15881589
if obj.dynamicAnchor:
15891590
self._dynamic_anchor_index.setdefault(root_key, {}).setdefault(obj.dynamicAnchor, ref_path)
15901591

@@ -1602,7 +1603,6 @@ def _resolve_recursive_ref(self, item: JsonSchemaObject, path: list[str]) -> str
16021603
anchors = self._recursive_anchor_index.get(root_key, [])
16031604
if not anchors:
16041605
return "#"
1605-
# Build root-relative path for comparison
16061606
root_len = len(root_key)
16071607
if root_len < len(path):
16081608
suffix_parts = path[root_len:]
@@ -1612,8 +1612,6 @@ def _resolve_recursive_ref(self, item: JsonSchemaObject, path: list[str]) -> str
16121612
current_ref = "#/" + "/".join(suffix_parts)
16131613
else:
16141614
current_ref = "#" # pragma: no cover
1615-
# Find the best matching anchor: path prefix with longest match
1616-
# best defaults to "#" (root anchor fallback)
16171615
best = "#"
16181616
best_len = 0
16191617
for anchor_ref in anchors:
@@ -3022,10 +3020,8 @@ def parse_item( # noqa: PLR0911, PLR0912, PLR0914, PLR0915
30223020
item,
30233021
root_type_path,
30243022
)
3025-
# Resolve $recursiveRef to $ref (JSON Schema 2019-09)
30263023
if item.recursiveRef and not item.ref:
30273024
return self.get_ref_data_type(self._resolve_recursive_ref(item, path) or "#")
3028-
# Resolve $dynamicRef to $ref (JSON Schema 2020-12)
30293025
if item.dynamicRef and not item.ref:
30303026
return self.get_ref_data_type(self._resolve_dynamic_ref(item) or item.dynamicRef)
30313027
if item.is_ref_with_nullable_only and item.ref:
@@ -4151,11 +4147,9 @@ def _parse_file( # noqa: PLR0912, PLR0915
41514147
# parse $id before parsing $ref
41524148
root_obj = self._validate_schema_object(raw, path_parts or ["#"])
41534149
self.parse_id(root_obj, path_parts)
4154-
# Build $recursiveAnchor index for root object
41554150
if root_obj.recursiveAnchor:
41564151
root_key = tuple(path_parts)
41574152
self._recursive_anchor_index.setdefault(root_key, []).append("#")
4158-
# Build $dynamicAnchor index for root object
41594153
if root_obj.dynamicAnchor:
41604154
root_key = tuple(path_parts)
41614155
self._dynamic_anchor_index.setdefault(root_key, {}).setdefault(root_obj.dynamicAnchor, "#")
@@ -4173,12 +4167,10 @@ def _parse_file( # noqa: PLR0912, PLR0915
41734167
definition_path = [*path_parts, schema_path, key]
41744168
obj = self._validate_schema_object(model, definition_path)
41754169
self.parse_id(obj, definition_path)
4176-
# Build $recursiveAnchor index for definitions
41774170
if obj.recursiveAnchor:
41784171
root_key = tuple(path_parts)
41794172
ref_path = "#/" + schema_path.lstrip("#/") + "/" + key
41804173
self._recursive_anchor_index.setdefault(root_key, []).append(ref_path)
4181-
# Build $dynamicAnchor index for definitions
41824174
if obj.dynamicAnchor:
41834175
root_key = tuple(path_parts)
41844176
ref_path = "#/" + schema_path.lstrip("#/") + "/" + key

0 commit comments

Comments
 (0)