Repository navigation
fix(downgrader): keep a nullable anyOf that a $ref points into in 3.1 to 3.0 - #92
Merged
Merged
Conversation
The nullable merge removes an `anyOf` of a branch and null, and a `oneOf` can become `anyOf`, so a `$ref` such as `#/.../home/anyOf/0` (written by zod-to-json-schema's default root `$refStrategy` and by swagger-parser `bundle()`) dangled in 3.0. Such refs are now replaced by their target, like refs into `$defs`. Also lists refs into keywords 3.0 removes as a README limitation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013DHN6pxmaQMB2i4Rv3Kx35
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the 3-file, single-commit fix for dangling $refs into anyOf/oneOf branches during 3.1→3.0 conversion.
- Inlining heuristic —
isInlinednow treats refs containing/anyOf/or/oneOf/as inline targets, so refs into those branches are converted to their targets instead of being left dangling when 3.0 merges or renames those keywords. - Regression coverage — the new test covers a Zod-style nullable
anyOfwhose second branch refs the first, plus a$refinto a survivingoneOf; verified it fails without the source change. - Documentation — README now describes the new inlining behavior and the remaining limitation for refs into removed keywords such as
prefixItems/0andpropertyNames.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
… to 3.0 Inlining such `$ref`s, as the previous commit did, reused the in-progress conversion of an ancestor for a recursive `$ref`, so the 3.0 output held a cycle and JSON.stringify threw. Instead, a `$ref` kept as written marks each schema whose `anyOf` it passes through, and the nullable merge (and its `oneOf` rename) skips those, so the `$ref` still resolves. `$ref`s into a `oneOf` that becomes `anyOf`, or into a removed keyword, are listed as a README limitation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013DHN6pxmaQMB2i4Rv3Kx35
dinwwwh
added a commit
that referenced
this pull request
Oct 24, 2026
…rgets (#95) <!-- claude-projects-attribution: {"github_login":"dinwwwh"} --> _Requested by **dinwwwh** · [project thread](https://claude.ai/code/project/chan_01UcKMz91tMarPV1kWLVgLYE?thread=cmsg_01UcKMz91tMarPV1kWLVgLYE5YzkoJZXQSvTxTCyxe76P9)_ ## Problem Since #92, branch coverage is 99.78%. In `convertSchemaRef` (3.1 to 3.0), `if (isRecord(holder))` is only false for a kept `$ref` whose part before `/anyOf/` doesn't resolve, which no test hits. ## Fix `anyOfTargets` becomes a `Set<unknown>` and the resolved value is added as is. The set is only queried with input schema objects in `downgradeV30`, so an unresolved value in it changes nothing. +2/−5. ## Checks - `pnpm run check` passes - 262 tests pass - Coverage is 100% for statements, branches, functions and lines - Output is byte-identical on 66 real documents --- _Generated by [Claude Code](https://claude.ai/code/session_01YNzPR6fb2NHVQPYKsD2eta)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Requested by dinwwwh · project thread
Fixes a regression from #87/#88. The nullable merge removes an
anyOfof a branch and null (or #88 turns it intooneOf), but a$refsuch as#/components/schemas/User/properties/home/anyOf/0was kept as written and dangled, making the 3.0 document invalid.Real producers of such refs: zod-to-json-schema 3.25 with its default
$refStrategy: "root"(z.object({ home: Addr.nullable(), work: Addr.nullable() })giveswork: { anyOf: [{ $ref: "#/properties/home/anyOf/0" }, { type: "null" }] }, and a recursiveNode.nullable()refs#/anyOf/0), and@apidevtools/swagger-parserbundle()with an external schema used nullable twice.Changes
convertSchemaRefmarks, for a$refkept as written, each input schema whoseanyOfthe$refpasses through. The deferred nullable merge skips those, so the pair stays ananyOfand the$refstill resolves. The marks are cleared per call. No copies are made, so a recursive$refcan't create a cycle.$refs instead, but a recursive one then reused the in-progress conversion of an ancestor, so the output held a JS cycle andJSON.stringifythrew. The second commit replaces that.home'sanyOfandwork's$ref, and a recursive$refinto#/anyOf/0keeps theanyOfand serializes. Both fail on main.anyOf;$refs into a keyword 3.0 removes (prefixItems/0,propertyNames) or into aoneOfthat becomesanyOfstill dangle (pre-existing, rarer).pnpm run checkand all 258 tests pass; corpus snapshots are unchanged. A spec-level check of the self-referencingoneOf, the recursiveanyOf, and the nullable pair gives valid 3.0 with no dangling refs.🤖 Generated with Claude Code
https://claude.ai/code/session_013DHN6pxmaQMB2i4Rv3Kx35