From 28466505f5387e38bb54faba34e85c51fe6bdb79 Mon Sep 17 00:00:00 2001 From: Louis Bompart Date: Tue, 4 Aug 2026 22:20:39 +0200 Subject: [PATCH 1/3] feat(typescript-zod): emit unknown schemas --- .../TypeScriptZod/TypeScriptZodRenderer.ts | 2 +- test/unit/typescript-zod-unknown.test.ts | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 test/unit/typescript-zod-unknown.test.ts diff --git a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts index d9d62c617..22a5974ca 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts @@ -106,7 +106,7 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { const match = matchType( t, - (_anyType) => "z.any()", + (_anyType) => "z.unknown()", (_nullType) => "z.null()", (_boolType) => "z.boolean()", (_integerType) => "z.number()", diff --git a/test/unit/typescript-zod-unknown.test.ts b/test/unit/typescript-zod-unknown.test.ts new file mode 100644 index 000000000..6ec4b829d --- /dev/null +++ b/test/unit/typescript-zod-unknown.test.ts @@ -0,0 +1,24 @@ +import { InputData, JSONSchemaInput, quicktype } from "quicktype-core"; +import { expect, test } from "vitest"; + +test("TypeScript Zod emits unknown for unconstrained JSON Schema values", async () => { + const schemaInput = new JSONSchemaInput(undefined); + await schemaInput.addSource({ + name: "TopLevel", + schema: JSON.stringify({ + type: "object", + properties: { + value: {}, + }, + required: ["value"], + }), + }); + const inputData = new InputData(); + inputData.addInput(schemaInput); + + const result = await quicktype({ inputData, lang: "typescript-zod" }); + const output = result.lines.join("\n"); + + expect(output).toContain("z.unknown()"); + expect(output).not.toContain("z.any()"); +}); From 7ce140734a051084cc9261e23daa8bf00d36bda5 Mon Sep 17 00:00:00 2001 From: Louis Bompart Date: Tue, 4 Aug 2026 22:45:39 +0200 Subject: [PATCH 2/3] feat(typescript-zod): add prefer-unknown --- .../TypeScriptZod/TypeScriptZodRenderer.ts | 13 +++++++-- .../src/language/TypeScriptZod/language.ts | 9 ++++-- test/unit/typescript-zod-unknown.test.ts | 28 ++++++++++++++++--- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts index 22a5974ca..4686ca6f1 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts @@ -33,6 +33,12 @@ import { legalizeName } from "../JavaScript/utils.js"; import type { typeScriptZodOptions } from "./language.js"; +type TypeScriptZodRendererOptions = Omit< + OptionValues, + "preferUnknown" +> & + Partial, "preferUnknown">>; + export class TypeScriptZodRenderer extends ConvenienceRenderer { /** TypeRefs of object types that participate in a reference cycle. * These must be emitted as z.lazy() schemas with an explicit type @@ -42,7 +48,7 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { public constructor( targetLanguage: TargetLanguage, renderContext: RenderContext, - protected readonly _options: OptionValues, + protected readonly _options: TypeScriptZodRendererOptions, ) { super(targetLanguage, renderContext); } @@ -106,7 +112,10 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { const match = matchType( t, - (_anyType) => "z.unknown()", + (_anyType) => + this._options.preferUnknown !== false + ? "z.unknown()" + : "z.any()", (_nullType) => "z.null()", (_boolType) => "z.boolean()", (_integerType) => "z.number()", diff --git a/packages/quicktype-core/src/language/TypeScriptZod/language.ts b/packages/quicktype-core/src/language/TypeScriptZod/language.ts index 63b9abab5..1552ed307 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod/language.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod/language.ts @@ -16,6 +16,11 @@ import { TypeScriptZodRenderer } from "./TypeScriptZodRenderer.js"; export const typeScriptZodOptions = { justSchema: new BooleanOption("just-schema", "Schema only", false), + preferUnknown: new BooleanOption( + "prefer-unknown", + "Use unknown instead of any", + true, + ), }; export const typeScriptZodLanguageConfig = { @@ -35,8 +40,8 @@ export class TypeScriptZodTargetLanguage extends TargetLanguage< super(typeScriptZodLanguageConfig); } - public getOptions(): Record { - return {}; + public getOptions(): typeof typeScriptZodOptions { + return typeScriptZodOptions; } public get stringTypeMapping(): StringTypeMapping { diff --git a/test/unit/typescript-zod-unknown.test.ts b/test/unit/typescript-zod-unknown.test.ts index 6ec4b829d..cc3ca7cab 100644 --- a/test/unit/typescript-zod-unknown.test.ts +++ b/test/unit/typescript-zod-unknown.test.ts @@ -1,7 +1,12 @@ -import { InputData, JSONSchemaInput, quicktype } from "quicktype-core"; +import { + InputData, + JSONSchemaInput, + type RendererOptions, + quicktype, +} from "quicktype-core"; import { expect, test } from "vitest"; -test("TypeScript Zod emits unknown for unconstrained JSON Schema values", async () => { +async function render(rendererOptions: RendererOptions = {}): Promise { const schemaInput = new JSONSchemaInput(undefined); await schemaInput.addSource({ name: "TopLevel", @@ -16,9 +21,24 @@ test("TypeScript Zod emits unknown for unconstrained JSON Schema values", async const inputData = new InputData(); inputData.addInput(schemaInput); - const result = await quicktype({ inputData, lang: "typescript-zod" }); - const output = result.lines.join("\n"); + const result = await quicktype({ + inputData, + lang: "typescript-zod", + rendererOptions, + }); + return result.lines.join("\n"); +} + +test("TypeScript Zod emits unknown for unconstrained JSON Schema values by default", async () => { + const output = await render(); expect(output).toContain("z.unknown()"); expect(output).not.toContain("z.any()"); }); + +test("TypeScript Zod emits any when prefer-unknown is disabled", async () => { + const output = await render({ "prefer-unknown": false }); + + expect(output).toContain("z.any()"); + expect(output).not.toContain("z.unknown()"); +}); From 2e250a55959bcd2350b0704886f9787642529a77 Mon Sep 17 00:00:00 2001 From: Louis Bompart Date: Tue, 4 Aug 2026 22:50:26 +0200 Subject: [PATCH 3/3] fix(typescript-zod): make prefer-unknown opt-in --- .../src/language/TypeScriptZod/language.ts | 2 +- test/unit/typescript-zod-unknown.test.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/quicktype-core/src/language/TypeScriptZod/language.ts b/packages/quicktype-core/src/language/TypeScriptZod/language.ts index 1552ed307..52e3e0070 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod/language.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod/language.ts @@ -19,7 +19,7 @@ export const typeScriptZodOptions = { preferUnknown: new BooleanOption( "prefer-unknown", "Use unknown instead of any", - true, + false, ), }; diff --git a/test/unit/typescript-zod-unknown.test.ts b/test/unit/typescript-zod-unknown.test.ts index cc3ca7cab..ef4b81d1c 100644 --- a/test/unit/typescript-zod-unknown.test.ts +++ b/test/unit/typescript-zod-unknown.test.ts @@ -29,16 +29,16 @@ async function render(rendererOptions: RendererOptions = {}): Promise { return result.lines.join("\n"); } -test("TypeScript Zod emits unknown for unconstrained JSON Schema values by default", async () => { +test("TypeScript Zod emits any for unconstrained JSON Schema values by default", async () => { const output = await render(); - expect(output).toContain("z.unknown()"); - expect(output).not.toContain("z.any()"); + expect(output).toContain("z.any()"); + expect(output).not.toContain("z.unknown()"); }); -test("TypeScript Zod emits any when prefer-unknown is disabled", async () => { - const output = await render({ "prefer-unknown": false }); +test("TypeScript Zod emits unknown when prefer-unknown is enabled", async () => { + const output = await render({ "prefer-unknown": true }); - expect(output).toContain("z.any()"); - expect(output).not.toContain("z.unknown()"); + expect(output).toContain("z.unknown()"); + expect(output).not.toContain("z.any()"); });