Skip to content

Handle JSON Schema 2020-12 prefixItems tuples#2949

Merged
schani merged 4 commits into
glideapps:masterfrom
Leonard013:fix/2811-prefixitems-tuple-schema
Jul 19, 2026
Merged

Handle JSON Schema 2020-12 prefixItems tuples#2949
schani merged 4 commits into
glideapps:masterfrom
Leonard013:fix/2811-prefixitems-tuple-schema

Conversation

@Leonard013

Copy link
Copy Markdown
Contributor

Description

JSON Schema inputs that describe a tuple with the 2020-12 prefixItems
keyword were silently mishandled: with --just-types the tool emitted
nothing at all, and via the API the tuple degraded to a bare any[], dropping
every member type. This teaches the JSON Schema input to treat prefixItems
the same way it already treats draft-07's array-valued items.

Related Issue

Fixes #2811

Root cause

In packages/quicktype-core/src/input/JSONSchemaInput.ts:

  1. makeArrayType() only read schema.items. When a schema used
    prefixItems instead (the 2020-12 rename of the array/tuple form of
    items, now emitted by Pydantic v2 and Rust's schemars), items was
    undefined, so the code fell through to the generic itemType = any
    branch and produced any[]. With a top-level array and --just-types,
    any[] needs no named declaration, so the output was empty (exit 0, no
    interface).
  2. The needUnion type-inference predicate (the schema.items !== undefined || ... chain) did not mention prefixItems, so a schema whose only
    array signal was prefixItems (no explicit type) was not even
    recognized as needing an array/union type.

Fix

  • makeArrayType() now uses schema.prefixItems as the tuple source when it
    is an array, running the exact same per-item union logic already used for
    array-valued items (with a prefixItems location for $ref resolution
    and diagnostics). When prefixItems is absent, behaviour is byte-for-byte
    unchanged.
  • schema.prefixItems !== undefined is added to the needUnion predicate so
    a bare prefixItems schema is recognized as an array type.

This matches the maintainer's directed fix from the triage note on #2811
("apply the existing Array.isArray(items) tuple-union logic to
schema.prefixItems as well, and add it to the type-inference predicate near
line 1232").

Tests

New unit test test/unit/prefix-items-schema.test.ts (mirrors the existing
windows-schema-paths.test.ts pattern — it drives the quicktype-core API
directly, which the fixture harness cannot do here because a data round-trip
still accepts an any[] degradation and would not notice the lost tuple
types):

  • a top-level type: array + prefixItems: [{$ref Bar}, {$ref Baz}] schema
    now keeps its member field types (barField, bazField) in the generated
    TypeScript;
  • a parallel draft-07 array-valued items schema still works (no
    regression).

Verification (before / after)

Reproduction schema foo.schema.json:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "array",
  "prefixItems": [{ "$ref": "#/$defs/Bar" }, { "$ref": "#/$defs/Baz" }],
  "$defs": {
    "Bar": { "type": "object", "properties": { "x": { "type": "integer" } }, "required": ["x"] },
    "Baz": { "type": "object", "properties": { "y": { "type": "string" } }, "required": ["y"] }
  }
}

Command:

node dist/index.js --lang typescript --src-lang schema --just-types --top-level Foo foo.schema.json

Before: empty output (exit 0, no interface emitted).

After:

export interface Foo {
    x?: number;
    y?: string;
    [property: string]: any;
}

(identical to the output the equivalent draft-07 array-form items schema
already produced.)

Regression test, before vs after the source change:

# with the fix reverted:
npx vitest run test/unit/prefix-items-schema.test.ts
  Tests  1 failed | 1 passed (2)   # prefixItems case fails (Foo was any[])

# with the fix:
npx vitest run test/unit/prefix-items-schema.test.ts
  Tests  2 passed (2)

Full unit suite: npx vitest run -> 72 passed (9 files).
Lint: npx biome check on both changed files -> clean.

Compatibility

Backward compatible. When prefixItems is not present the array-handling
path is unchanged, so all existing draft-07 items behaviour is preserved
(covered by the existing tuple.schema fixture and the new regression test's
draft-07 case). When both prefixItems and a rest items schema are present,
prefixItems drives the tuple element union — consistent with how the
existing code already ignores additionalItems for array-form items.


AI-authored and AI-reviewed; not yet human-reviewed.

Leonard013 and others added 3 commits July 19, 2026 10:27
JSON Schema draft 2020-12 renamed the array (tuple) form of `items` to
`prefixItems`, which is now emitted by Pydantic v2 and Rust's schemars.
`makeArrayType()` only read `schema.items`, so a `prefixItems`-based tuple
fell through to the generic `any[]` branch and lost its member types; with
`--just-types` and a top-level array that produced no output at all.
`prefixItems` was also missing from the `needUnion` type-inference predicate.

Apply the existing array-form `items` tuple-union logic to `schema.prefixItems`
as well, and add `prefixItems` to the type-inference predicate. When
`prefixItems` is absent, array handling is unchanged.

Adds test/unit/prefix-items-schema.test.ts, which fails before this change and
passes after.

Fixes glideapps#2811
Two follow-ups to the prefixItems tuple support:

- In 2020-12, an object-form `items` next to `prefixItems` describes the
  rest elements of an open tuple. It was previously dropped; now its type
  joins the tuple's element union (quicktype models tuples as arrays of a
  union of the member types). Boolean `items` (`false` closes the tuple,
  `true` allows anything) is still ignored.

- Add `test/inputs/schema/prefix-items.schema` so the feature is covered
  by the end-to-end fixture tests, mirroring `tuple.schema` but with
  2020-12 `prefixItems`, plus an open tuple that also has a rest `items`
  schema. The `.fail.union.json` sample has a tuple element outside the
  declared union, so validating languages must reject it — it would be
  accepted if the tuple degraded to `any[]`, which is exactly the
  regression this guards against.

The unit test keeps only what fixtures cannot express (that `$ref`d
member types survive into the generated code) and gains a case for the
prefixItems-plus-rest-items union.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generated cJSON deserializer silently drops array elements whose type
is outside the union instead of aborting parsing, so the
prefix-items.1.fail.union.json expected-failure sample does not fail.
This is the already-documented "Union, Map and Arrays with invalid types
are not checked" limitation; add the schema to that skip group.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@schani
schani force-pushed the fix/2811-prefixitems-tuple-schema branch from 4c42e72 to 3a00877 Compare July 19, 2026 14:28
Both test drivers exit 0 on a failed decode (scala3 prints the circe
DecodingFailure, haskell encodes the Maybe result as null), so the
prefix-items.1.fail.union.json expected-failure sample cannot be
detected. Same documented limitation as nested-intersection-union.schema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@schani
schani merged commit 3b5580a into glideapps:master Jul 19, 2026
24 checks passed
@schani

schani commented Jul 19, 2026

Copy link
Copy Markdown
Member

Thank you so much!

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.

[BUG]: no TS schema generated for array-based tuple schemas with prefixitems

2 participants