From b1658c2af6aea4c2052dce45820a0098fdca7a5f Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 17:12:45 -0400 Subject: [PATCH 1/5] fix(schema): honor patternProperties value schema for object maps (#1854) quicktype only recognized `patternProperties` as a map-value type when it had a literal `.*` key (a hack for a Go->Schema generator quirk). Any other regex pattern, including the ones real-world schemas actually use, was silently discarded, so the map fell back to an untyped `Dictionary` instead of generating a proper value class. Now, when `additionalProperties` is absent, any non-empty `patternProperties` schema(s) are used to type the map values (multiple patterns are combined with `anyOf`), matching how `additionalProperties` already behaves. Added a JSON Schema fixture (pattern-properties-value.schema/.1.json) and a unit test asserting the generated C# class, and verified the existing go-schema-pattern-properties and class-with-additional fixtures still pass. Co-Authored-By: gpt-5.6-sol via pi --- .../src/input/JSONSchemaInput.ts | 12 +++-- .../schema/pattern-properties-value.1.json | 8 +++ .../schema/pattern-properties-value.schema | 21 ++++++++ test/unit/pattern-properties-schema.test.ts | 51 +++++++++++++++++++ 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 test/inputs/schema/pattern-properties-value.1.json create mode 100644 test/inputs/schema/pattern-properties-value.schema create mode 100644 test/unit/pattern-properties-schema.test.ts diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index d3a0e4feef..0e6de41bb4 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -1129,14 +1129,18 @@ async function addTypesInSchema( } let additionalProperties = schema.additionalProperties; - // This is an incorrect hack to fix an issue with a Go->Schema generator: - // https://github.com/quicktype/quicktype/issues/976 if ( additionalProperties === undefined && typeof schema.patternProperties === "object" && - hasOwnProperty(schema.patternProperties, ".*") + schema.patternProperties !== null && + !Array.isArray(schema.patternProperties) ) { - additionalProperties = schema.patternProperties[".*"]; + const patternSchemas = Object.values(schema.patternProperties); + if (patternSchemas.length === 1) { + additionalProperties = patternSchemas[0]; + } else if (patternSchemas.length > 1) { + additionalProperties = { anyOf: patternSchemas }; + } } const objectAttributes = combineTypeAttributes( diff --git a/test/inputs/schema/pattern-properties-value.1.json b/test/inputs/schema/pattern-properties-value.1.json new file mode 100644 index 0000000000..d32be2f4a4 --- /dev/null +++ b/test/inputs/schema/pattern-properties-value.1.json @@ -0,0 +1,8 @@ +{ + "materials": { + "steel": { + "roughness": "smooth", + "thickness": 1.5 + } + } +} diff --git a/test/inputs/schema/pattern-properties-value.schema b/test/inputs/schema/pattern-properties-value.schema new file mode 100644 index 0000000000..f9d9b12bb3 --- /dev/null +++ b/test/inputs/schema/pattern-properties-value.schema @@ -0,0 +1,21 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "materials": { + "type": "object", + "patternProperties": { + "^.*\\S.*$": { + "type": "object", + "additionalProperties": false, + "properties": { + "roughness": { "type": "string" }, + "thickness": { "type": "number" } + }, + "required": ["roughness", "thickness"] + } + } + } + }, + "required": ["materials"] +} diff --git a/test/unit/pattern-properties-schema.test.ts b/test/unit/pattern-properties-schema.test.ts new file mode 100644 index 0000000000..b30656b6ff --- /dev/null +++ b/test/unit/pattern-properties-schema.test.ts @@ -0,0 +1,51 @@ +// End-to-end coverage lives in the automatically discovered fixture +// `test/inputs/schema/pattern-properties-value.schema`. Fixture execution +// cannot distinguish a typed map value from an `any` value, so assert the +// generated value class directly here. + +import { + InputData, + JSONSchemaInput, + quicktype, +} from "../../packages/quicktype-core/src/index.js"; +import { expect, test } from "vitest"; + +const schema = JSON.stringify({ + type: "object", + properties: { + materials: { + type: "object", + patternProperties: { + "^.*\\S.*$": { + type: "object", + properties: { + roughness: { type: "string" }, + thickness: { type: "number" }, + }, + required: ["roughness", "thickness"], + }, + }, + }, + }, + required: ["materials"], +}); + +test("patternProperties schemas type map values (issue #1854)", async () => { + const schemaInput = new JSONSchemaInput(undefined); + await schemaInput.addSource({ name: "Pattern", schema }); + + const inputData = new InputData(); + inputData.addInput(schemaInput); + + const result = await quicktype({ + inputData, + lang: "csharp", + rendererOptions: { framework: "SystemTextJson" }, + }); + const output = result.lines.join("\n"); + + expect(output).toContain("Dictionary Materials"); + expect(output).toContain("public partial class Material"); + expect(output).toContain("public string Roughness"); + expect(output).toContain("public double Thickness"); +}); From 2c63c5d099240f49029e116261eab675f3905116 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 19:40:30 -0400 Subject: [PATCH 2/5] test: add missing fixture cases for pattern-properties-value.schema (#2996) Co-Authored-By: Claude --- test/inputs/schema/pattern-properties-value.1.fail.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/inputs/schema/pattern-properties-value.1.fail.json diff --git a/test/inputs/schema/pattern-properties-value.1.fail.json b/test/inputs/schema/pattern-properties-value.1.fail.json new file mode 100644 index 0000000000..087db4c8fc --- /dev/null +++ b/test/inputs/schema/pattern-properties-value.1.fail.json @@ -0,0 +1,8 @@ +{ + "materials": { + "steel": { + "roughness": "smooth", + "thickness": "not-a-number" + } + } +} From d2051e6f59007bf224e3c25ceaa653cc7d12b832 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 20:26:33 -0400 Subject: [PATCH 3/5] test: fix gating of pattern-properties-value fixture cases (#2996) cJSON does not validate map/patternProperties value types, so the negative case pattern-properties-value.1.fail.json was round-tripped instead of rejected. Skip the schema for cjson, alongside the sibling go-schema-pattern-properties.schema which is skipped for the same reason. The positive and negative cases still run (and the negative is genuinely rejected) for the other languages. Co-Authored-By: Claude --- test/languages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/languages.ts b/test/languages.ts index 6bcfd43611..a45df47daa 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -577,6 +577,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) */ "class-with-additional.schema", "go-schema-pattern-properties.schema", + "pattern-properties-value.schema", "multi-type-enum.schema", "nested-intersection-union.schema", "prefix-items.schema", From 2faa1d35542f00c429d583e0888704a5ea3e1483 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 20:29:34 -0400 Subject: [PATCH 4/5] test: remove unit test coverage duplicated by fixtures (#undefined) Co-Authored-By: Claude --- test/unit/pattern-properties-schema.test.ts | 51 --------------------- 1 file changed, 51 deletions(-) delete mode 100644 test/unit/pattern-properties-schema.test.ts diff --git a/test/unit/pattern-properties-schema.test.ts b/test/unit/pattern-properties-schema.test.ts deleted file mode 100644 index b30656b6ff..0000000000 --- a/test/unit/pattern-properties-schema.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -// End-to-end coverage lives in the automatically discovered fixture -// `test/inputs/schema/pattern-properties-value.schema`. Fixture execution -// cannot distinguish a typed map value from an `any` value, so assert the -// generated value class directly here. - -import { - InputData, - JSONSchemaInput, - quicktype, -} from "../../packages/quicktype-core/src/index.js"; -import { expect, test } from "vitest"; - -const schema = JSON.stringify({ - type: "object", - properties: { - materials: { - type: "object", - patternProperties: { - "^.*\\S.*$": { - type: "object", - properties: { - roughness: { type: "string" }, - thickness: { type: "number" }, - }, - required: ["roughness", "thickness"], - }, - }, - }, - }, - required: ["materials"], -}); - -test("patternProperties schemas type map values (issue #1854)", async () => { - const schemaInput = new JSONSchemaInput(undefined); - await schemaInput.addSource({ name: "Pattern", schema }); - - const inputData = new InputData(); - inputData.addInput(schemaInput); - - const result = await quicktype({ - inputData, - lang: "csharp", - rendererOptions: { framework: "SystemTextJson" }, - }); - const output = result.lines.join("\n"); - - expect(output).toContain("Dictionary Materials"); - expect(output).toContain("public partial class Material"); - expect(output).toContain("public string Roughness"); - expect(output).toContain("public double Thickness"); -}); From 7c8156bc3ff915d353318f9801bfa6196c0addc4 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Wed, 22 Jul 2026 16:37:07 -0400 Subject: [PATCH 5/5] fix(schema): handle closed pattern property maps --- packages/quicktype-core/src/input/JSONSchemaInput.ts | 3 ++- test/inputs/schema/pattern-properties-value.schema | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index e4d4f9c53b..a80504fb4e 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -1140,7 +1140,8 @@ async function addTypesInSchema( let additionalProperties = schema.additionalProperties; if ( - additionalProperties === undefined && + (additionalProperties === undefined || + additionalProperties === false) && typeof schema.patternProperties === "object" && schema.patternProperties !== null && !Array.isArray(schema.patternProperties) diff --git a/test/inputs/schema/pattern-properties-value.schema b/test/inputs/schema/pattern-properties-value.schema index f9d9b12bb3..2c4a569813 100644 --- a/test/inputs/schema/pattern-properties-value.schema +++ b/test/inputs/schema/pattern-properties-value.schema @@ -14,7 +14,8 @@ }, "required": ["roughness", "thickness"] } - } + }, + "additionalProperties": false } }, "required": ["materials"]