Skip to content

fix(typescript): emit named type alias for root-level array types#3033

Open
schani wants to merge 6 commits into
masterfrom
agent/fix-issue-1309
Open

fix(typescript): emit named type alias for root-level array types#3033
schani wants to merge 6 commits into
masterfrom
agent/fix-issue-1309

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

Bug

TypeScript did not generate correct types when a JSON Schema's root type is an array of objects.

For this schema:

{
    "$ref": "#/definitions/GetResp",
    "definitions": {
        "GetResp": { "title": "GetResp", "type": "array", "items": { "$ref": "#/definitions/SomeObject" } },
        "SomeObject": {
            "title": "SomeObject",
            "type": "object",
            "additionalProperties": false,
            "properties": { "name": { "type": "string" } },
            "required": ["name"]
        }
    }
}

TypeScript generated:

export interface PR {
    name: string;
}

— the array wrapper was silently dropped and the object interface was named after the array (PR, from the root array's title/filename) instead of after the object (SomeObject), and there was no named array type at all.

Go, by contrast, correctly generates type PR []SomeObject plus a separate type SomeObject struct {...}.

Root cause

TypeScriptRenderer inherited JavaScriptRenderer#namedTypeToNameForTopLevel, which uses directlyReachableSingleNamedType to collapse a root type onto the single named type reachable through it — including through an ArrayType. For a root array of objects, this assigns the top-level name to the item object type instead of the array itself, and TypeScript (unlike some other languages) never separately emits a named alias for array top levels, so the array wrapper disappears entirely.

Fix

In packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptRenderer.ts:

  • Override namedTypeToNameForTopLevel to return undefined for ArrayType, so root-level arrays keep their own top-level name instead of collapsing onto their item type (this mirrors the same pattern already used by other renderers, e.g. DartRenderer, GolangRenderer, CSharpRenderer).
  • Emit export type <Name> = <Item>[]; for root-level array types in emitTypes.
  • Use the array alias (rather than an inline array-of-item-type expression) in the Convert class's method signatures and in the "how to use" usage comment.

Flow and other renderers are unaffected.

Test coverage

  • test/inputs/schema/root-array-ref.schema (+ .1.json sample) — new end-to-end JSON Schema fixture exercising a root-level array of a named object type, run via the existing schema-* fixture machinery for every language (compiles/round-trips for all languages, including TypeScript).
  • test/unit/typescript-root-array-schema.test.ts — unit test asserting the exact generated structure (export interface SomeObject, export type PR = SomeObject[];, and that export interface PR is not generated), since the fixture round-trip test alone can't express "these exact type names/structure must be produced."

Verification (local)

  • npm run build passes.
  • Repro re-run after the fix now produces the expected output:
    export interface SomeObject {
        name: string;
    }
    
    export type PR = SomeObject[];
  • QUICKTEST=true FIXTURE=schema-typescript script/test — full suite passes for all schema fixtures including the new root-array-ref.schema; the only failure (vega-lite.schema TS2411 errors from tsc) was confirmed to be pre-existing on unmodified master (verified by stashing this fix and re-running), unrelated to this change.
  • npx vitest run test/unit — all 164 unit tests across 26 files pass, including the new test.
  • CI will additionally validate the full fixture matrix across all languages.

Fixes #1309

🤖 Generated with Claude Code

schani and others added 6 commits July 20, 2026 18:08
)

TypeScript collapsed a root-level array-of-objects type into a single
interface named after the array (using directlyReachableSingleNamedType),
dropping the array wrapper entirely and emitting no array type alias. Other
languages such as Go correctly emit a named array alias plus a separately
named object type.

Fix: override namedTypeToNameForTopLevel in TypeScriptRenderer to stop
collapsing ArrayType top levels into their item type, and emit an
`export type Foo = Bar[];` alias for root-level array types, updating
converter signatures/usage comments to reference it.

Added test/inputs/schema/root-array-ref.schema (+ .1.json sample) as an
end-to-end fixture, and test/unit/typescript-root-array-schema.test.ts to
assert the exact naming/structure (fixture round-trip tests alone can't
express that).

Fixes #1309

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts:
#	packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptRenderer.ts
The new root-array-ref.schema fixture is a top-level array whose item
type carries its own title. Three languages cannot round-trip it and
were failing CI:

- kotlinx: top-level arrays render as `typealias TopLevel = JsonArray<T>`,
  which doesn't compile (same documented limitation as union.schema).
- php: the fixture driver does not support top-level arrays (same as
  union.schema).
- typescript-zod: the renderer emits only the titled item schema
  (SomeObjectSchema) and no TopLevelElementSchema, so the driver can't
  locate a top-level schema to parse the array against.

Skip the fixture for these three, mirroring the existing union.schema
precedent, rather than weakening the fixture for languages that handle
it correctly. The positive and negative cases still run for TypeScript
and the other schema languages.

Co-Authored-By: Claude <noreply@anthropic.com>
…uired

The root-array-ref negative sample ([{}], an array element missing the
required "name" property) relies on required-property enforcement. Four
more languages already skip required.schema because their generated code
or driver cannot detect a missing required property, so the new fixture's
fail sample is likewise not rejected and the fixture fails:

- cjson: required properties absent are not checked.
- swift: on Linux the missing-required-property failure is not rejected.
- haskell: the driver encodes the Maybe result, so a failed decode prints
  null and exits 0.
- elixir: struct keys cannot be enforced at runtime.

Skip root-array-ref.schema for each, mirroring their existing
required.schema skips. The positive and negative cases still run for the
languages that do enforce required (TypeScript, C#, Java, Rust, etc.).

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Generated-output differences

70 files differ — 45 modified, 25 new, 0 deleted
2439 changed lines — +2111 / −328

Open the generated-output report →

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.

Typescript doesn't generate correct types when root element is an array of objects

1 participant