From 45e67006c356e130761bc623bc6c96741a5a2fc3 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 16:38:29 -0400 Subject: [PATCH] fix(json-schema): recognize $defs when deriving type names (#2778) The definitionName getter in JSONSchemaInput.ts only recognized the legacy "definitions" keyword, so types defined under the standard "$defs" keyword (draft 2019-09+) never had their schema-given name treated as a given name. combineNames then merged the type's other candidate names by common prefix, producing truncated/wrong names (e.g. "LightParams" became "Light"). Fix: also recognize "$defs" in definitionName. Adds a schema fixture (light.schema/light.1.json) exercising the round trip, and a focused unit test asserting the generated TypeScript interface keeps its given name, since fixture tests don't observe generated symbol names. Co-Authored-By: gpt-5.6-sol via pi --- .../src/input/JSONSchemaInput.ts | 2 +- test/inputs/schema/light.1.json | 7 +++ test/inputs/schema/light.schema | 21 ++++++++ test/unit/json-schema-definitions.test.ts | 51 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 test/inputs/schema/light.1.json create mode 100644 test/inputs/schema/light.schema create mode 100644 test/unit/json-schema-definitions.test.ts diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index d3a0e4feef..30678943dc 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -306,7 +306,7 @@ export class Ref { public get definitionName(): string | undefined { const pe = arrayGetFromEnd(this.path, 2); if (pe === undefined) return undefined; - if (keyOrIndex(pe) === "definitions") + if (keyOrIndex(pe) === "definitions" || keyOrIndex(pe) === "$defs") return keyOrIndex(defined(arrayLast(this.path))); return undefined; } diff --git a/test/inputs/schema/light.1.json b/test/inputs/schema/light.1.json new file mode 100644 index 0000000000..478f25fe74 --- /dev/null +++ b/test/inputs/schema/light.1.json @@ -0,0 +1,7 @@ +{ + "LightParams": { + "outlet_id": "outlet-1", + "app_id": "app-1", + "rgba": "255,255,255,1" + } +} diff --git a/test/inputs/schema/light.schema b/test/inputs/schema/light.schema new file mode 100644 index 0000000000..42c4b43b64 --- /dev/null +++ b/test/inputs/schema/light.schema @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "LightParams": { + "type": "object", + "properties": { + "outlet_id": { "type": "string" }, + "app_id": { "type": "string" }, + "rgba": { "type": "string" } + }, + "additionalProperties": false, + "required": ["outlet_id", "app_id", "rgba"] + } + }, + "type": "object", + "properties": { + "LightParams": { "$ref": "#/$defs/LightParams" } + }, + "additionalProperties": false, + "required": ["LightParams"] +} diff --git a/test/unit/json-schema-definitions.test.ts b/test/unit/json-schema-definitions.test.ts new file mode 100644 index 0000000000..f63702d495 --- /dev/null +++ b/test/unit/json-schema-definitions.test.ts @@ -0,0 +1,51 @@ +import { + FetchingJSONSchemaStore, + InputData, + JSONSchemaInput, + quicktype, +} from "quicktype-core"; +import { expect, test } from "vitest"; + +const lightSchema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $defs: { + LightParams: { + type: "object", + properties: { + outlet_id: { type: "string" }, + app_id: { type: "string" }, + rgba: { type: "string" }, + }, + additionalProperties: false, + required: ["outlet_id", "app_id", "rgba"], + }, + }, + type: "object", + properties: { + LightParams: { $ref: "#/$defs/LightParams" }, + }, + additionalProperties: false, + required: ["LightParams"], +}; + +test("a type under $defs keeps its definition name (issue #2778)", async () => { + const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); + await schemaInput.addSource({ + name: "LightSchema", + schema: JSON.stringify(lightSchema), + }); + const inputData = new InputData(); + inputData.addInput(schemaInput); + + const result = await quicktype({ + inputData, + lang: "typescript", + rendererOptions: { "just-types": "true" }, + }); + const output = result.lines.join("\n"); + + // The fixture exercises this schema end-to-end, but generated symbol names + // are not observable at runtime, so assert the regression here. + expect(output).toContain("LightParams: LightParams;"); + expect(output).toContain("export interface LightParams {"); +});