JSON Schema 2020-12: `$ref` resolution with `$id`/`$anchor`
### Issue workflow progress
_Progress of the issue based on the
[Contributor Workflow](https://github.com/the-guild-org/Stack/blob/master/CONTRIBUTING.md#a-typical-contributor-workflow)_
- [x] 1. The issue provides a reproduction (MRE attached with synthetic specs, see below)
- [ ] 2. A failing test has been provided
- [ ] 3. A local solution has been provided
- [ ] 4. A pull request is pending review
---
**Describe the bug**
We hit this while trying to load the [Petstore 3.1 spec](https://petstore31.swagger.io/) through `loadOpenAPISubgraph`. It fails with `ENOENT` errors because `json-machete`'s `dereferenceObject` treats `$id`-based `$ref` URIs as filesystem paths.
The Petstore 3.1 spec uses two JSON Schema 2020-12 reference patterns that break:
`$id` as a `$ref` target: `Category` declares `$id: /api/v31/components/schemas/category`, and `PetDetails.category` references it with `$ref: /api/v31/components/schemas/category`. `dereferenceObject` splits on `#`, sees no fragment, and calls `readFile('/api/v31/components/schemas/category')`.
`$anchor` fragment references: `PetDetails.id` declares `$anchor: pet_details_id` inside a schema with `$id: /api/v31/components/schemas/petdetails`. `Pet.petDetailsId` uses `$ref: /api/v31/components/schemas/petdetails#pet_details_id`. The left side gets the same file-path treatment, and `pet_details_id` is interpreted as a JSON Pointer when it's actually an anchor name.
Worth noting: OpenAPI 3.1 specs that stick to JSON Pointer refs (`#/components/schemas/...`) work fine. The problem is specifically `$ref` resolution when `$id`/`$anchor` semantics are involved.
**To Reproduce**
[json-schema-2020-12-edge-cases.tgz](https://github.com/user-attachments/files/26098850/json-schema-2020-12-edge-cases.tgz)
The attached MRE has four minimal OpenAPI 3.1 specs, each isolating one feature:
```sh
npx tsx reproduce.ts
```
```
JSON Schema 2020-12 Edge Cases — @omnigraph/openapi
════════════════════════════════════════════════════
@omnigraph/openapi: 0.109.36
Origin: Petstore 3.1 (https://petstore31.swagger.io/)
✓ Standard JSON Pointer $ref ("#/components/schemas/...")
7 types: Query, Item, Category, HTTPMethod, ObjMap, String, Boolean
✗ $id URI used as $ref target (JSON Schema 2020-12 §8.2.1)
Unable to load /schemas/category from <cwd>/specs
✗ $anchor + $id#anchor $ref pattern (JSON Schema 2020-12 §8.2.2)
Unable to load /schemas/details from <cwd>/specs
✓ $ref with sibling keywords (OpenAPI 3.1 / JSON Schema 2020-12)
10 types: Query, Item, Int, Mutation, JSON, Item_Input, HTTPMethod, ObjMap, String, Boolean
────────────────────────────────────────────────────
2 passed, 2 failed
```
MRE files:
- `reproduce.ts` — calls `loadOpenAPISubgraph` directly for each spec
- `specs/control.yaml` — standard `$ref`, passes (baseline)
- `specs/id-based-ref.yaml` — `$id` URI as `$ref` target, fails
- `specs/anchor-ref.yaml` — `$anchor` + `$id#anchor` pattern, fails
- `specs/ref-sibling-keywords.yaml` — `$ref` with sibling keywords, passes
**Expected behavior**
When a `$ref` value matches a `$id` declared elsewhere in the same document, `dereferenceObject` should resolve it against that schema rather than treating it as a file path. Similarly, fragment references like `$ref: /schemas/details#details_id` should look up `details_id` as an `$anchor`, not as a JSON Pointer.
**Environment:**
- OS: macOS (darwin 25.3.0)
- `@omnigraph/openapi`: 0.109.36
- `json-machete`: 0.97.6
- NodeJS: 25.x
**Additional context**
These aren't contrived edge cases; they come directly from the Petstore 3.1 spec maintained by the Swagger team. The `$id`/`$anchor` patterns are standard JSON Schema 2020-12 (§8.2.1, §8.2.2) and part of what OpenAPI 3.1 adopted when it aligned with JSON Schema.
From reading the `json-machete` source, it looks like `dereferenceObject` would need to index `$id` declarations during traversal and check them before falling through to file/URL resolution. Happy to discuss further or help with a PR if that'd be useful.
关闭于 2026-03-20 3 条评论