You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit eca9687
Browse filesBrowse the repository at this point in the historyBrowse files
fix(v4): enforce the six JSON Schema keywords fromJSONSchema silently dropped (#6535)
* fix(v4): enforce the six JSON Schema keywords fromJSONSchema silently dropped
`minProperties`/`maxProperties` fold into the pre-pipe guard that already enforces `propertyNames`, so they count the raw input's own keys. `uniqueItems` and `contains`/`minContains`/`maxContains` run as checks on the converted array schema, with structural equality for duplicates. All six are carried into metadata like `propertyNames`, so `toJSONSchema` round-trips them.
No public API and no file outside from-json-schema.ts, so mini and non-users pay nothing.
Closes#6463
* fix: evaluate the array keywords on the raw instance, pre-pipe
`uniqueItems` and the `contains` family moved into a pre-pipe guard mirroring `checkObjectGuards`: a nested `default` in `items` made the post-parse checks accept a `contains` the instance fails and reject distinct items as duplicates. The property-count issues carry explicit messages, since no locale has a Sizable entry for `object`.
* test: pin the two side effects of the pre-pipe guards
A guard issue aborts the pipe, so `minItems`/`maxItems` are not co-reported, and the guard's identity transform makes `toJSONSchema` strip `default`/`examples` from the input-mode document. Both are the cost of evaluating the keywords against the raw instance; recording them as tests so they read as deliberate.
* perf: detect uniqueItems duplicates by canonical key instead of pairwise
The pairwise deep-equal scan was quadratic on the parse path: an all-distinct array of 4,000 objects cost 246ms, and nothing bounds the length a converted schema accepts. Each element now hashes to an injective string, so detection is a Map lookup â the same array costs 5ms, and 20,000 elements cost 22ms where the old scan would have taken seconds. A cycle or a NaN, neither expressible in JSON, yields no key and is never called a duplicate; the old comparison would have recursed forever on the cycle.
* fix: short-circuit the contains scan and drop carried subschemas that cannot resolve
`contains` now stops one past `maxContains`, since nothing above the ceiling changes the verdict; the message for that direction no longer names a count it stopped measuring. A carried `contains` or `propertyNames` travels without the root `$defs` its `$ref`s point into, so one holding a reference is dropped rather than re-emitted as a dangling pointer in an otherwise valid document.
* fix: do not read a $ref key inside instance data as a schema reference
`default`, `const`, `enum` and `examples` hold instance data, where `$ref` is an ordinary property name. Descending into them made the carry drop a `contains` or `propertyNames` that was perfectly resolvable, widening the emitted schema.
* fix: walk only schema positions when looking for a carried $ref
Excluding the four standard instance keywords still left every unknown annotation traversed, so an `x-` extension holding a `$ref` key dropped a constraint that resolves. Inverted to an allowlist of the positions a schema can actually occupy.
* fix: walk draft-7 dependencies when looking for a carried $ref
@@ -519,20 +710,25 @@ function convertBaseSchema(schema: JSONSchema.JSONSchema, ctx: ConversionContext
519
710
}
520
711
521
712
// propertyNames constrains key *names* only, and says nothing about which keys are required or how their values validate. Layering it on top of the result keeps properties/patternProperties/additionalProperties composing underneath. `true` allows every name, so it needs no guard.
// `propertyNames` is enforced by a key guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares.
// `propertyNames` and `contains` are enforced by a guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares. A carried subschema is copied verbatim while the root `$defs` stay behind, so one holding a `$ref` is dropped rather than emitted as a dangling reference.
0 commit comments