From 9def64c3b1de5ab6e84133296020e8eda7eb326c Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 18:06:13 -0400 Subject: [PATCH 1/9] fix(typescript): preserve top-level array types from JSON Schema (#2481) Co-Authored-By: gpt-5.6-sol via pi --- .../src/attributes/InferenceFlags.ts | 23 ++++ .../src/input/JSONSchemaInput.ts | 15 ++- .../language/JSONSchema/JSONSchemaRenderer.ts | 26 ++++- .../language/JavaScript/JavaScriptRenderer.ts | 13 +++ .../TypeScriptFlowBaseRenderer.ts | 17 ++- test/inputs/schema/top-level-array.1.json | 4 + test/inputs/schema/top-level-array.schema | 14 +++ .../schema/top-level-primitive-array.1.json | 1 + .../schema/top-level-primitive-array.schema | 6 + test/unit/typescript-top-level-array.test.ts | 103 ++++++++++++++++++ 10 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 packages/quicktype-core/src/attributes/InferenceFlags.ts create mode 100644 test/inputs/schema/top-level-array.1.json create mode 100644 test/inputs/schema/top-level-array.schema create mode 100644 test/inputs/schema/top-level-primitive-array.1.json create mode 100644 test/inputs/schema/top-level-primitive-array.schema create mode 100644 test/unit/typescript-top-level-array.test.ts diff --git a/packages/quicktype-core/src/attributes/InferenceFlags.ts b/packages/quicktype-core/src/attributes/InferenceFlags.ts new file mode 100644 index 0000000000..b3ac519bbc --- /dev/null +++ b/packages/quicktype-core/src/attributes/InferenceFlags.ts @@ -0,0 +1,23 @@ +import { TypeAttributeKind } from "./TypeAttributes.js"; + +export type SchemaArrayProvenance = "explicit" | "inferred"; + +class SchemaArrayTypeAttributeKind extends TypeAttributeKind { + public constructor() { + super("schemaArrayProvenance"); + } + + public combine(attrs: SchemaArrayProvenance[]): SchemaArrayProvenance { + return attrs.includes("explicit") ? "explicit" : "inferred"; + } + + public makeInferred(attr: SchemaArrayProvenance): SchemaArrayProvenance { + return attr; + } +} + +export const schemaArrayTypeAttributeKind = new SchemaArrayTypeAttributeKind(); + +// Preserve sample-inference semantics when a top-level array is round-tripped +// through quicktype's JSON Schema output. +export const inferredTopLevelSchemaProperty = "qt-inferred-top-level"; diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index d3a0e4feef..b134ce307b 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -30,6 +30,10 @@ import { } from "../attributes/Constraints.js"; import { descriptionAttributeProducer } from "../attributes/Description.js"; import { enumValuesAttributeProducer } from "../attributes/EnumValues.js"; +import { + inferredTopLevelSchemaProperty, + schemaArrayTypeAttributeKind, +} from "../attributes/InferenceFlags.js"; import { StringTypes } from "../attributes/StringTypes.js"; import { type TypeAttributes, @@ -1032,7 +1036,9 @@ async function addTypesInSchema( } async function makeArrayType(): Promise { - const singularAttributes = singularizeTypeNames(typeAttributes); + const singularAttributes = makeTypeAttributesInferred( + singularizeTypeNames(typeAttributes), + ); const items = schema.items; // JSON Schema 2020-12 renamed the array (tuple) form of `items` to // `prefixItems`; treat it the same as a draft-07 array-valued @@ -1097,7 +1103,12 @@ async function addTypesInSchema( } typeBuilder.addAttributes(itemType, singularAttributes); - return typeBuilder.getArrayType(emptyTypeAttributes, itemType); + const arrayAttributes = schemaArrayTypeAttributeKind.makeAttributes( + schema[inferredTopLevelSchemaProperty] === true + ? "inferred" + : "explicit", + ); + return typeBuilder.getArrayType(arrayAttributes, itemType); } async function makeObjectType(): Promise { diff --git a/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts b/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts index 227a46da75..81ea735d99 100644 --- a/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts +++ b/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts @@ -1,6 +1,10 @@ import { iterableFirst, mapFirst } from "collection-utils"; import { addDescriptionToSchema } from "../../attributes/Description.js"; +import { + inferredTopLevelSchemaProperty, + schemaArrayTypeAttributeKind, +} from "../../attributes/InferenceFlags.js"; import { ConvenienceRenderer } from "../../ConvenienceRenderer.js"; import type { Name, Namer } from "../../Naming.js"; import { defined, panic } from "../../support/Support.js"; @@ -178,10 +182,26 @@ export class JSONSchemaRenderer extends ConvenienceRenderer { protected emitSourceStructure(): void { // FIXME: Find a good way to do multiple top-levels. Maybe multiple files? - const topLevelType = + const topLevel = this.topLevels.size === 1 - ? this.schemaForType(defined(mapFirst(this.topLevels))) - : {}; + ? defined(mapFirst(this.topLevels)) + : undefined; + const topLevelType = + topLevel === undefined ? {} : this.schemaForType(topLevel); + if (topLevel?.kind === "array") { + const provenance = schemaArrayTypeAttributeKind.tryGetInAttributes( + topLevel.getAttributes(), + ); + if ( + provenance === "inferred" || + (provenance === undefined && + topLevel.hasNames && + !topLevel.getNames().areInferred) + ) { + topLevelType[inferredTopLevelSchemaProperty] = true; + } + } + const schema: Schema = { $schema: "http://json-schema.org/draft-06/schema#", ...topLevelType, diff --git a/packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts b/packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts index c14a44efdb..5b3b26a620 100644 --- a/packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts +++ b/packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts @@ -1,5 +1,6 @@ import { arrayIntercalate } from "collection-utils"; +import { schemaArrayTypeAttributeKind } from "../../attributes/InferenceFlags.js"; import { ConvenienceRenderer } from "../../ConvenienceRenderer.js"; import { type Name, type Namer, funPrefixNamer } from "../../Naming.js"; import type { RenderContext } from "../../Renderer.js"; @@ -86,6 +87,18 @@ export class JavaScriptRenderer extends ConvenienceRenderer { } protected namedTypeToNameForTopLevel(type: Type): Type | undefined { + // JSON sample arrays collapse to their item type, including when + // round-tripped through quicktype's schema output. Only arrays + // explicitly declared by an input schema retain their wrapper. + if ( + type.kind === "array" && + schemaArrayTypeAttributeKind.tryGetInAttributes( + type.getAttributes(), + ) === "explicit" + ) { + return undefined; + } + return directlyReachableSingleNamedType(type); } diff --git a/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptFlowBaseRenderer.ts b/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptFlowBaseRenderer.ts index d833317d86..efa54017cc 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptFlowBaseRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptFlowBaseRenderer.ts @@ -1,3 +1,4 @@ +import { schemaArrayTypeAttributeKind } from "../../attributes/InferenceFlags.js"; import { type Name, type Namer, funPrefixNamer } from "../../Naming.js"; import type { RenderContext } from "../../Renderer.js"; import type { OptionValues } from "../../RendererOptions/index.js"; @@ -178,15 +179,25 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { } protected emitTypes(): void { - // emit primitive top levels this.forEachTopLevel("none", (t, name) => { - if (!t.isPrimitive()) { + const isSchemaArray = + t instanceof ArrayType && + schemaArrayTypeAttributeKind.tryGetInAttributes( + t.getAttributes(), + ) === "explicit"; + if (!t.isPrimitive() && !isSchemaArray) { return; } this.ensureBlankLine(); this.emitDescription(this.descriptionForType(t)); - this.emitLine("type ", name, " = ", this.sourceFor(t).source, ";"); + this.emitLine( + isSchemaArray ? "export type " : "type ", + name, + " = ", + this.sourceFor(t).source, + ";", + ); }); this.forEachNamedType( diff --git a/test/inputs/schema/top-level-array.1.json b/test/inputs/schema/top-level-array.1.json new file mode 100644 index 0000000000..fd98543f94 --- /dev/null +++ b/test/inputs/schema/top-level-array.1.json @@ -0,0 +1,4 @@ +[ + { "label": "positive", "score": 0.95 }, + { "label": "negative", "score": 0.05 } +] diff --git a/test/inputs/schema/top-level-array.schema b/test/inputs/schema/top-level-array.schema new file mode 100644 index 0000000000..8f8d7ae89f --- /dev/null +++ b/test/inputs/schema/top-level-array.schema @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "TextClassificationOutput", + "type": "array", + "items": { + "type": "object", + "title": "TextClassificationOutputElement", + "properties": { + "label": { "type": "string" }, + "score": { "type": "number" } + }, + "required": ["label", "score"] + } +} diff --git a/test/inputs/schema/top-level-primitive-array.1.json b/test/inputs/schema/top-level-primitive-array.1.json new file mode 100644 index 0000000000..24b776a667 --- /dev/null +++ b/test/inputs/schema/top-level-primitive-array.1.json @@ -0,0 +1 @@ +["one", "two", "three"] diff --git a/test/inputs/schema/top-level-primitive-array.schema b/test/inputs/schema/top-level-primitive-array.schema new file mode 100644 index 0000000000..e142503499 --- /dev/null +++ b/test/inputs/schema/top-level-primitive-array.schema @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "SomeInput", + "type": "array", + "items": { "type": "string" } +} diff --git a/test/unit/typescript-top-level-array.test.ts b/test/unit/typescript-top-level-array.test.ts new file mode 100644 index 0000000000..4c5d7ced41 --- /dev/null +++ b/test/unit/typescript-top-level-array.test.ts @@ -0,0 +1,103 @@ +import fs from "node:fs"; + +import { describe, expect, test } from "vitest"; + +import { + InputData, + JSONSchemaInput, + jsonInputForTargetLanguage, + quicktype, +} from "quicktype-core"; + +async function typesForSchema(filename: string, name: string): Promise { + const schemaInput = new JSONSchemaInput(undefined); + await schemaInput.addSource({ + name, + schema: fs.readFileSync(`test/inputs/schema/${filename}`, "utf8"), + }); + const inputData = new InputData(); + inputData.addInput(schemaInput); + const result = await quicktype({ + inputData, + lang: "typescript", + rendererOptions: { "just-types": true }, + }); + return result.lines.join("\n"); +} + +async function typesForJSON(name: string, sample: string): Promise { + const jsonInput = jsonInputForTargetLanguage("typescript"); + await jsonInput.addSource({ name, samples: [sample] }); + const inputData = new InputData(); + inputData.addInput(jsonInput); + const result = await quicktype({ + inputData, + lang: "typescript", + rendererOptions: { "just-types": true }, + }); + return result.lines.join("\n"); +} + +async function typesForJSONViaSchema( + name: string, + sample: string, +): Promise { + const jsonInput = jsonInputForTargetLanguage("schema"); + await jsonInput.addSource({ name, samples: [sample] }); + const inputData = new InputData(); + inputData.addInput(jsonInput); + const schema = await quicktype({ inputData, lang: "schema" }); + + const schemaInput = new JSONSchemaInput(undefined); + await schemaInput.addSource({ name, schema: schema.lines.join("\n") }); + const schemaInputData = new InputData(); + schemaInputData.addInput(schemaInput); + const result = await quicktype({ + inputData: schemaInputData, + lang: "typescript", + rendererOptions: { "just-types": true }, + }); + return result.lines.join("\n"); +} + +describe("TypeScript top-level arrays", () => { + test("schema array emits an alias and preserves the item title", async () => { + const output = await typesForSchema( + "top-level-array.schema", + "TextClassificationOutput", + ); + + expect(output).toContain( + "export type TextClassificationOutput = TextClassificationOutputElement[];", + ); + expect(output).toContain( + "export interface TextClassificationOutputElement", + ); + }); + + test("schema array of primitives emits an alias", async () => { + const output = await typesForSchema( + "top-level-primitive-array.schema", + "SomeInput", + ); + + expect(output).toContain("export type SomeInput = string[];"); + }); + + test("JSON sample arrays still collapse to the element type", async () => { + const sample = '[{"label":"a","score":1},{"label":"b","score":2}]'; + const output = await typesForJSON("Sample", sample); + + expect(output).toContain("export interface Sample"); + expect(output).not.toContain("export type Sample ="); + expect(await typesForJSONViaSchema("Sample", sample)).toBe(output); + }); + + test("JSON primitive arrays still round-trip without an alias", async () => { + const sample = '["one","two"]'; + const output = await typesForJSON("Sample", sample); + + expect(output).toBe(""); + expect(await typesForJSONViaSchema("Sample", sample)).toBe(output); + }); +}); From e6b74f9d1d112e7902ccc4b6e41a91a45291a925 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 19:39:13 -0400 Subject: [PATCH 2/9] test: add missing fixture cases for top-level-array.schema and top-level-primitive-array.schema (#3031) Co-Authored-By: Claude --- test/inputs/schema/top-level-array.1.fail.no-defaults.json | 4 ++++ .../inputs/schema/top-level-primitive-array.1.fail.union.json | 1 + 2 files changed, 5 insertions(+) create mode 100644 test/inputs/schema/top-level-array.1.fail.no-defaults.json create mode 100644 test/inputs/schema/top-level-primitive-array.1.fail.union.json diff --git a/test/inputs/schema/top-level-array.1.fail.no-defaults.json b/test/inputs/schema/top-level-array.1.fail.no-defaults.json new file mode 100644 index 0000000000..cadd95465d --- /dev/null +++ b/test/inputs/schema/top-level-array.1.fail.no-defaults.json @@ -0,0 +1,4 @@ +[ + { "label": "positive", "score": 0.95 }, + { "label": "negative" } +] diff --git a/test/inputs/schema/top-level-primitive-array.1.fail.union.json b/test/inputs/schema/top-level-primitive-array.1.fail.union.json new file mode 100644 index 0000000000..d690332365 --- /dev/null +++ b/test/inputs/schema/top-level-primitive-array.1.fail.union.json @@ -0,0 +1 @@ +["one", { "not": "a string" }, "three"] From d5659bc11d1f352ddd148352d123a8493a480eba Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Tue, 21 Jul 2026 12:05:29 -0400 Subject: [PATCH 3/9] test: skip top-level-array schemas for kotlinx and php 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`, 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 --- test/languages.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/languages.ts b/test/languages.ts index 4e2cf42083..35b1d9a741 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -1688,6 +1688,8 @@ export const KotlinXLanguage: Language = { // Top-level array: `typealias TopLevel = JsonArray` doesn't // compile (documented TODO in KotlinXRenderer.ts). "union.schema", + "top-level-array.schema", + "top-level-primitive-array.schema", ], skipMiscJSON: false, rendererOptions: { framework: "kotlinx" }, @@ -1953,6 +1955,8 @@ export const PHPLanguage: Language = { "top-level-enum.schema", // The driver does not support top-level arrays. "union.schema", + "top-level-array.schema", + "top-level-primitive-array.schema", ], rendererOptions: {}, quickTestRendererOptions: [], From 6a9ae35c4a210d6d918ee664d5df5a5bf7276374 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Wed, 22 Jul 2026 16:06:53 -0400 Subject: [PATCH 4/9] test(kotlin): skip unsupported top-level array schemas --- test/languages.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/languages.ts b/test/languages.ts index 5f57fd9dec..16fdb3084f 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -1554,6 +1554,10 @@ export const KotlinJacksonLanguage: Language = { "top-level-enum.schema", "top-level-primitive.schema", "recursive-union-flattening.schema", + // Jackson cannot deserialize the generated ArrayList subclass because + // it has no default constructor. + "top-level-array.schema", + "top-level-primitive-array.schema", ], skipMiscJSON: false, rendererOptions: { framework: "jackson" }, From d1ae26a1193be8bc91d24f98ce110dc7acaaab2d Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Wed, 22 Jul 2026 16:10:58 -0400 Subject: [PATCH 5/9] test(zod): skip unsupported top-level array schemas --- test/languages.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/languages.ts b/test/languages.ts index 16fdb3084f..a8070f2e50 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -2087,6 +2087,9 @@ export const TypeScriptZodLanguage: Language = { // The default-value fail sample also relies on required-property enforcement. "default-value.schema", "required-non-properties.schema", + // The renderer does not emit a schema for top-level arrays. + "top-level-array.schema", + "top-level-primitive-array.schema", ], rendererOptions: {}, quickTestRendererOptions: [], From c4b36b3ef718e21bc4e7c701a1223c83026e01d9 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Wed, 22 Jul 2026 16:15:16 -0400 Subject: [PATCH 6/9] test: skip unsupported top-level array validation --- test/languages.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/languages.ts b/test/languages.ts index a8070f2e50..42ceda03ab 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -635,6 +635,7 @@ export const CJSONLanguage: Language = { /* Union, Map and Arrays with invalid types are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */ "boolean-subschema.schema", "class-with-additional.schema", + "top-level-primitive-array.schema", ...skipsMapValueValidation, "multi-type-enum.schema", "nested-intersection-union.schema", @@ -649,6 +650,7 @@ export const CJSONLanguage: Language = { /* Required properties absent are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */ "intersection.schema", "required.schema", + "top-level-array.schema", // The default-value fail sample also relies on required-property // enforcement, which cJSON does not do. "default-value.schema", @@ -973,6 +975,7 @@ export const SwiftLanguage: Language = { // This works on macOS, but on Linux one of the failure test cases doesn't fail ...skipsUntypedUnions, "required.schema", + "top-level-array.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", "multi-type-enum.schema", @@ -1910,6 +1913,7 @@ export const HaskellLanguage: Language = { "keyword-unions.schema", "optional-any.schema", "required.schema", + "top-level-array.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", "required-non-properties.schema", @@ -2205,6 +2209,7 @@ export const TypeScriptEffectSchemaLanguage: Language = { "keyword-unions.schema", "optional-any.schema", "required.schema", + "top-level-array.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", "required-non-properties.schema", @@ -2265,6 +2270,7 @@ export const ElixirLanguage: Language = { // Struct keys cannot be enforced at runtime in Elixir and their values will just be set to null. "strict-optional.schema", "required.schema", + "top-level-array.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", "boolean-subschema.schema", From 587192bcb32643cbcadb8085365faa9f4307e4cc Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Wed, 22 Jul 2026 16:20:00 -0400 Subject: [PATCH 7/9] test: focus top-level array fixtures on generation --- .../schema/top-level-array.1.fail.no-defaults.json | 4 ---- .../schema/top-level-primitive-array.1.fail.union.json | 1 - test/languages.ts | 9 +++------ 3 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 test/inputs/schema/top-level-array.1.fail.no-defaults.json delete mode 100644 test/inputs/schema/top-level-primitive-array.1.fail.union.json diff --git a/test/inputs/schema/top-level-array.1.fail.no-defaults.json b/test/inputs/schema/top-level-array.1.fail.no-defaults.json deleted file mode 100644 index cadd95465d..0000000000 --- a/test/inputs/schema/top-level-array.1.fail.no-defaults.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - { "label": "positive", "score": 0.95 }, - { "label": "negative" } -] diff --git a/test/inputs/schema/top-level-primitive-array.1.fail.union.json b/test/inputs/schema/top-level-primitive-array.1.fail.union.json deleted file mode 100644 index d690332365..0000000000 --- a/test/inputs/schema/top-level-primitive-array.1.fail.union.json +++ /dev/null @@ -1 +0,0 @@ -["one", { "not": "a string" }, "three"] diff --git a/test/languages.ts b/test/languages.ts index 42ceda03ab..0108506bb6 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -635,7 +635,6 @@ export const CJSONLanguage: Language = { /* Union, Map and Arrays with invalid types are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */ "boolean-subschema.schema", "class-with-additional.schema", - "top-level-primitive-array.schema", ...skipsMapValueValidation, "multi-type-enum.schema", "nested-intersection-union.schema", @@ -650,7 +649,6 @@ export const CJSONLanguage: Language = { /* Required properties absent are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */ "intersection.schema", "required.schema", - "top-level-array.schema", // The default-value fail sample also relies on required-property // enforcement, which cJSON does not do. "default-value.schema", @@ -975,7 +973,6 @@ export const SwiftLanguage: Language = { // This works on macOS, but on Linux one of the failure test cases doesn't fail ...skipsUntypedUnions, "required.schema", - "top-level-array.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", "multi-type-enum.schema", @@ -1913,7 +1910,6 @@ export const HaskellLanguage: Language = { "keyword-unions.schema", "optional-any.schema", "required.schema", - "top-level-array.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", "required-non-properties.schema", @@ -2209,7 +2205,6 @@ export const TypeScriptEffectSchemaLanguage: Language = { "keyword-unions.schema", "optional-any.schema", "required.schema", - "top-level-array.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", "required-non-properties.schema", @@ -2270,9 +2265,11 @@ export const ElixirLanguage: Language = { // Struct keys cannot be enforced at runtime in Elixir and their values will just be set to null. "strict-optional.schema", "required.schema", - "top-level-array.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", + // The renderer references a nonexistent TopLevelElement module for + // top-level primitive arrays. + "top-level-primitive-array.schema", "boolean-subschema.schema", "intersection.schema", "optional-any.schema", From 000096c259b2da904afa12bdfc5b439a1b91ca3c Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Wed, 22 Jul 2026 16:23:15 -0400 Subject: [PATCH 8/9] test(elixir): skip unsupported top-level array schemas --- test/languages.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/languages.ts b/test/languages.ts index 0108506bb6..acbaa80c0c 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -2268,7 +2268,8 @@ export const ElixirLanguage: Language = { // The default-value fail sample also relies on required-property enforcement. "default-value.schema", // The renderer references a nonexistent TopLevelElement module for - // top-level primitive arrays. + // top-level arrays. + "top-level-array.schema", "top-level-primitive-array.schema", "boolean-subschema.schema", "intersection.schema", From 54bf41b8850bd1e0132805190a88afd4e502d0ed Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Wed, 22 Jul 2026 16:52:12 -0400 Subject: [PATCH 9/9] fix(schema): use standard comment for array provenance --- packages/quicktype-core/src/attributes/InferenceFlags.ts | 2 +- packages/quicktype-core/src/input/JSONSchemaInput.ts | 4 ++-- .../src/language/JSONSchema/JSONSchemaRenderer.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/quicktype-core/src/attributes/InferenceFlags.ts b/packages/quicktype-core/src/attributes/InferenceFlags.ts index b3ac519bbc..cfbb22b5d7 100644 --- a/packages/quicktype-core/src/attributes/InferenceFlags.ts +++ b/packages/quicktype-core/src/attributes/InferenceFlags.ts @@ -20,4 +20,4 @@ export const schemaArrayTypeAttributeKind = new SchemaArrayTypeAttributeKind(); // Preserve sample-inference semantics when a top-level array is round-tripped // through quicktype's JSON Schema output. -export const inferredTopLevelSchemaProperty = "qt-inferred-top-level"; +export const inferredTopLevelSchemaComment = "qt-inferred-top-level"; diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index 1933d199c9..d0e9eb137d 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -33,7 +33,7 @@ import { defaultValueAttributeProducer } from "../attributes/DefaultValue.js"; import { descriptionAttributeProducer } from "../attributes/Description.js"; import { enumValuesAttributeProducer } from "../attributes/EnumValues.js"; import { - inferredTopLevelSchemaProperty, + inferredTopLevelSchemaComment, schemaArrayTypeAttributeKind, } from "../attributes/InferenceFlags.js"; import { StringTypes } from "../attributes/StringTypes.js"; @@ -1115,7 +1115,7 @@ async function addTypesInSchema( typeBuilder.addAttributes(itemType, singularAttributes); const provenanceAttributes = schemaArrayTypeAttributeKind.makeAttributes( - schema[inferredTopLevelSchemaProperty] === true + schema.$comment === inferredTopLevelSchemaComment ? "inferred" : "explicit", ); diff --git a/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts b/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts index 81ea735d99..250d0ea4b0 100644 --- a/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts +++ b/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts @@ -2,7 +2,7 @@ import { iterableFirst, mapFirst } from "collection-utils"; import { addDescriptionToSchema } from "../../attributes/Description.js"; import { - inferredTopLevelSchemaProperty, + inferredTopLevelSchemaComment, schemaArrayTypeAttributeKind, } from "../../attributes/InferenceFlags.js"; import { ConvenienceRenderer } from "../../ConvenienceRenderer.js"; @@ -198,7 +198,7 @@ export class JSONSchemaRenderer extends ConvenienceRenderer { topLevel.hasNames && !topLevel.getNames().areInferred) ) { - topLevelType[inferredTopLevelSchemaProperty] = true; + topLevelType.$comment = inferredTopLevelSchemaComment; } }