Skip to content

Report a root $ref onto a keyword value as not a JSON Schema, not as a missing pointer - #883

Draft
claude[bot] wants to merge 1 commit into
masterfrom
claude/fix-root-ref-onto-keyword-value-8gw65n
Draft

claude[bot] wants to merge 1 commit into
masterfrom
claude/fix-root-ref-onto-keyword-value-8gw65n

Conversation

@claude

@claude claude Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

A schema whose root $ref is a JSON Pointer onto a keyword's value in the same document now fails with the message every other position already gets, instead of a wrong "missing pointer". The schema is still rejected; only the error changes. No generated output changes.

Before (master, since the ref-parser 16 upgrade in #842):

$ json2ts schema.json
error: MissingPointerError: Missing $ref pointer "#/definitions/unit/default". Token "definitions" does not exist.

definitions exists; the pointer leads to the string "metres".

After:

$ json2ts schema.json
error: ReferenceError: $ref "#/definitions/unit/default" at the root resolves to the string "metres", which is not a JSON Schema.
A $ref in place of a schema must lead to an object or a boolean.

for

{"$ref": "#/definitions/unit/default", "definitions": {"unit": {"type": "string", "default": "metres"}}}

A root pointer that leads nowhere (#/definitions/nope) still reports the ref-parser's MissingPointerError, as before.

Why

resolveRootRef (prenormalizer) merges a root $ref into the root when it leads to a schema in the same document, and leaves anything else to the ref-parser. Ref-parser 16 follows the root's own $ref while walking that very pointer's tokens, lands on the string, and fails on the next token inside its own resolution, so the non-schema-target guard added in #877 never sees the value. Ref-parser 11 handed the string to the guard, which is why master before #842 printed the right message.

How

  • dereference() (resolver) now looks the root's #/… pointer up itself before either dereferencing route. A defined target that is not an object or boolean is rejected through the existing rejectNonSchemaTargets, so the wording, the second line and the ReferenceError type are the same as for a property. An undefined target is still left to the ref-parser (MissingPointerError).
  • The pointer walk resolveRootRef already had (own keys only, ~0/~1 and percent decoding) moves to an exported inDocumentPointerValue, so both callers read the same value; no behaviour change there.
  • New e2e error case refToNonSchemaValue.root.ts beside refToNonSchemaValue.ts; it fails on master and passes here.

External files and non-root $refs are untouched: a root $ref to a file, a pointer into a file, and pointers below the root already got #877's message and still do.

Tests

  • CI=true bun run test: 681 pass, 0 fail, 351 snapshots (master 5caacfc: 680 / 0 / 351). No .snap file changes.
  • Corpus (node test/corpus/run.js --e2e --out …) on master and this head: 78 outputs byte-identical, 0 diagnostics.
  • A 125-row probe of $refs onto non-schema values in every schema position, run on master and this head: identical on 124 rows; the one that changes is the root in-document pointer (MissingPointerError → the message above). A 25-row two-route probe of the resolver's hook sequence: identical on 24 rows, the same one changes.
  • Also checked by hand on this head: root pointer onto a number (minLength), null (default: null), an enum item, an escaped key (a~1b), a two-hop chain ending on a value, a title sibling, the $RefParser route ($refOptions.dereference.circular), and compileFiles with such a root: each gives the message above. Root pointer onto true/false, onto a schema, and onto a missing key: unchanged.

Changelog

  • A root $ref pointing at a keyword's value in the same document (#/definitions/unit/default) is now reported as "resolves to the string …, which is not a JSON Schema" instead of a misleading MissingPointerError.

…s a missing pointer

A schema whose root `$ref` is a pointer onto a keyword's value in the same
document (`#/definitions/unit/default`) used to fail with the ref-parser's
`MissingPointerError: … Token "definitions" does not exist`, which is wrong:
`definitions` exists. The ref-parser (16 on) follows the root's own `$ref`
while walking that very pointer's tokens, lands on the string and fails the
next token, so the non-schema-target guard in `dereference` never saw the
value. It now judges the root's pointer itself before either dereferencing
route, with the same message every other position gets:

  $ref "#/definitions/unit/default" at the root resolves to the string
  "metres", which is not a JSON Schema.

A root pointer that leads nowhere still reports the ref-parser's
MissingPointerError. The pointer walk `resolveRootRef` already used moves to
`inDocumentPointerValue` so both callers read the same value.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant