fix(typescript): preserve top-level array types from JSON Schema#3031
Merged
Conversation
Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
…vel-primitive-array.schema (#3031) Co-Authored-By: Claude <noreply@anthropic.com>
Resolve conflict in JSONSchemaInput.ts makeArrayType: master added an arrayAttributes parameter (from minMaxItems forArray producers) while this branch adds schemaArrayTypeAttributeKind provenance. Combine both via combineTypeAttributes, and keep the branch's makeTypeAttributesInferred wrapping of singularAttributes. Co-Authored-By: Claude <noreply@anthropic.com>
The new top-level-array.schema and top-level-primitive-array.schema fixtures are top-level arrays, which neither the kotlinx renderer nor the php driver can handle -- the same reason both already skip union.schema. kotlinx emits `typealias TopLevel = JsonArray<T>`, which does not compile (JsonArray takes no type arguments), and the php driver does not support top-level arrays. Add both new schemas to those two skipSchema lists. Co-Authored-By: Claude <noreply@anthropic.com>
Generated-output differences57 files differ — 3 modified, 54 new, 0 deleted |
# Conflicts: # test/languages.ts
Generated-output differences63 files differ — 5 modified, 58 new, 0 deleted |
schani
commented
Jul 23, 2026
|
|
||
| // Preserve sample-inference semantics when a top-level array is round-tripped | ||
| // through quicktype's JSON Schema output. | ||
| export const inferredTopLevelSchemaComment = "qt-inferred-top-level"; |
Member
Author
There was a problem hiding this comment.
This is stupid. Remove this, and fix the fallout properly!
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.
The bug
For a JSON Schema whose top-level type is an
array, TypeScript (and JavaScript/Flow, which share the renderer) silently dropped the array wrapper and the element's own title. Given:{ "title": "TextClassificationOutput", "type": "array", "items": { "type": "object", "title": "TextClassificationOutputElement", "properties": { "label": { "type": "string" }, "score": { "type": "number" } }, "required": ["label", "score"] } }quicktype generated:
— no array-ness, and the element's own
title(TextClassificationOutputElement) discarded.--lang goon the same schema was correct (type Schema []TextClassificationOutputElementplus a separate struct), confirming this was TS/JS/Flow-specific. A top-level array of primitives ({"type": "array", "items": {"type": "string"}, "title": "SomeInput"}) produced no output at all with--just-types.Root cause
JavaScriptRenderer.namedTypeToNameForTopLevelunconditionally collapses a top-level array to its single named element type viadirectlyReachableSingleNamedType. That collapse is the correct, intentional behavior for JSON-sample-derived top-level arrays (a list of sample instances of one type — there's no title to preserve, and quicktype conventionally treats a top-level JSON-sample array as "one interface, many instances"). But it was applied unconditionally, so it also fired for JSON-Schema-derived top-level arrays, where the schema explicitly declares"type": "array"and the item's own title is meaningful and must be preserved.The fix
schemaArrayProvenancetype attribute (packages/quicktype-core/src/attributes/InferenceFlags.ts) thatJSONSchemaInputsets to"explicit"for arrays declared in real input JSON Schema, and to"inferred"for arrays that came from sample-based inference.JavaScriptRenderer.namedTypeToNameForTopLevelnow only collapses the array when it is not marked"explicit", so schema-declared top-level arrays keep theirtype X = Y[]alias and the element keeps its own schema-given name.TypeScriptFlowBaseRenderer.emitTypesnow also emits the array-alias line (export type X = Y[];) for these explicit schema arrays, matching how primitive top levels are already emitted.--lang schemaoutput is used internally to validate generated code (thediffViaSchemafixture check converts a JSON sample to a schema and regenerates from it, asserting identical output to direct-from-JSON generation), the schema writer (JSONSchemaRenderer) now marks aqt-inferred-top-levelproperty on schemas it emits for arrays that were themselves sample-inferred, so that round-tripping through quicktype's own schema output doesn't change a sample-array's collapsed shape into an explicit schema-array shape.Test coverage
test/inputs/schema/top-level-array.schema(titled object array, the original repro) andtest/inputs/schema/top-level-primitive-array.schema(primitive array), each with a matching.1.jsonsample — these run automatically for TypeScript/JavaScript/Flow fixture suites (not in anyskipSchemalist).test/unit/typescript-top-level-array.test.tscovering: schema array of objects emits the alias + titled interface, schema array of primitives emits the alias, and JSON-sample arrays still collapse to a single interface (no regression to existing sample-based behavior).Verification
npm run buildpasses.npm run test:unit— 167 tests pass.type Schema []TextClassificationOutputElement, modulo per-language convention).union.schemafixture (top-level array of a union) all produce correct output.QUICKTEST=true FIXTURE=typescript script/test— 78/78 pass (confirmed clean twice; one earlier run showed 2 failures that did not reproduce standalone and disappeared on rerun once I stopped running another fixture suite concurrently — resource contention in the parallel test harness, not a code issue).QUICKTEST=true FIXTURE=javascript script/test— 57/57 pass.flowtoolchain isn't installed in this environment, so its fixture suite wasn't runnable locally; CI will validate it).Fixes #2481
🤖 Generated with Claude Code