From 926c52769f0c2c6d88c907092041f86e5b5e0e85 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 7 Sep 2026 11:21:20 -0700 Subject: [PATCH 1/3] fix(effect-schema): preserve prototype-named map keys --- .../TypeScriptEffectSchemaRenderer.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/quicktype-core/src/language/TypeScriptEffectSchema/TypeScriptEffectSchemaRenderer.ts b/packages/quicktype-core/src/language/TypeScriptEffectSchema/TypeScriptEffectSchemaRenderer.ts index b61a3764b2..4df2c713f5 100644 --- a/packages/quicktype-core/src/language/TypeScriptEffectSchema/TypeScriptEffectSchemaRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptEffectSchema/TypeScriptEffectSchemaRenderer.ts @@ -155,9 +155,9 @@ export class TypeScriptEffectSchemaRenderer extends ConvenienceRenderer { }, (_classType) => panic("Should already be handled."), (_mapType) => [ - "S.Record({ key: S.String, value: ", + "mapSchema(", this.typeMapTypeFor(_mapType.values, false), - "})", + ")", ], (_enumType) => panic("Should already be handled."), (unionType) => { @@ -406,6 +406,26 @@ export class TypeScriptEffectSchemaRenderer extends ConvenienceRenderer { } this.emitImports(); + if (this.haveMaps) { + this.emitMultiline(` +const objectSchema = () => + S.declare( + (input): input is Record => + typeof input === "object" && input !== null && !Array.isArray(input) + ); +const mapSchema = (value: S.Schema) => { + const entries = S.transform(objectSchema(), S.Array(S.Tuple(S.String, value)), { + strict: false, + decode: Object.entries, + encode: Object.fromEntries + }); + return S.transform(entries, objectSchema(), { + strict: false, + decode: Object.fromEntries, + encode: Object.entries + }); +};`); + } this.emitSchemas(); } } From 1e4f074905b4854981c556990bdd3b2ee9c33d53 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 7 Sep 2026 18:12:43 -0700 Subject: [PATCH 2/3] test(effect-schema): scope map helper emission --- .../effect-schema-map-helper-emission.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/unit/effect-schema-map-helper-emission.test.ts diff --git a/test/unit/effect-schema-map-helper-emission.test.ts b/test/unit/effect-schema-map-helper-emission.test.ts new file mode 100644 index 0000000000..67cd6c6ee7 --- /dev/null +++ b/test/unit/effect-schema-map-helper-emission.test.ts @@ -0,0 +1,38 @@ +import { + InputData, + JSONSchemaInput, + quicktype, +} from "../../packages/quicktype-core/src/index.js"; +import { expect, test } from "vitest"; + +async function renderEffectSchema(schema: object): Promise { + const schemaInput = new JSONSchemaInput(undefined); + await schemaInput.addSource({ + name: "TopLevel", + schema: JSON.stringify(schema), + }); + const inputData = new InputData(); + inputData.addInput(schemaInput); + const result = await quicktype({ + inputData, + lang: "typescript-effect-schema", + }); + return result.lines.join("\n"); +} + +test("Effect Schema emits the map helper only for map types", async () => { + const objectOutput = await renderEffectSchema({ + type: "object", + properties: { value: { type: "boolean" } }, + }); + const mapOutput = await renderEffectSchema({ + type: "object", + additionalProperties: { type: "boolean" }, + }); + + expect(objectOutput).not.toContain("const mapSchema"); + expect(objectOutput).not.toContain("const objectSchema"); + expect(mapOutput).toContain("const mapSchema"); + expect(mapOutput).toContain("const objectSchema"); + expect(mapOutput).toContain("mapSchema(S.Boolean)"); +}); From e5892473444624959d9436c1f99f17991d6b3e0e Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 7 Sep 2026 20:02:35 -0700 Subject: [PATCH 3/3] test(effect-schema): rely on fixture coverage --- .../effect-schema-map-helper-emission.test.ts | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 test/unit/effect-schema-map-helper-emission.test.ts diff --git a/test/unit/effect-schema-map-helper-emission.test.ts b/test/unit/effect-schema-map-helper-emission.test.ts deleted file mode 100644 index 67cd6c6ee7..0000000000 --- a/test/unit/effect-schema-map-helper-emission.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - InputData, - JSONSchemaInput, - quicktype, -} from "../../packages/quicktype-core/src/index.js"; -import { expect, test } from "vitest"; - -async function renderEffectSchema(schema: object): Promise { - const schemaInput = new JSONSchemaInput(undefined); - await schemaInput.addSource({ - name: "TopLevel", - schema: JSON.stringify(schema), - }); - const inputData = new InputData(); - inputData.addInput(schemaInput); - const result = await quicktype({ - inputData, - lang: "typescript-effect-schema", - }); - return result.lines.join("\n"); -} - -test("Effect Schema emits the map helper only for map types", async () => { - const objectOutput = await renderEffectSchema({ - type: "object", - properties: { value: { type: "boolean" } }, - }); - const mapOutput = await renderEffectSchema({ - type: "object", - additionalProperties: { type: "boolean" }, - }); - - expect(objectOutput).not.toContain("const mapSchema"); - expect(objectOutput).not.toContain("const objectSchema"); - expect(mapOutput).toContain("const mapSchema"); - expect(mapOutput).toContain("const objectSchema"); - expect(mapOutput).toContain("mapSchema(S.Boolean)"); -});