diff --git a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts index d9d62c617..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.any()", + (_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..52e3e0070 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", + false, + ), }; 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 new file mode 100644 index 000000000..ef4b81d1c --- /dev/null +++ b/test/unit/typescript-zod-unknown.test.ts @@ -0,0 +1,44 @@ +import { + InputData, + JSONSchemaInput, + type RendererOptions, + quicktype, +} from "quicktype-core"; +import { expect, test } from "vitest"; + +async function render(rendererOptions: RendererOptions = {}): Promise { + 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", + rendererOptions, + }); + return result.lines.join("\n"); +} + +test("TypeScript Zod emits any for unconstrained JSON Schema values by default", async () => { + const output = await render(); + + expect(output).toContain("z.any()"); + expect(output).not.toContain("z.unknown()"); +}); + +test("TypeScript Zod emits unknown when prefer-unknown is enabled", async () => { + const output = await render({ "prefer-unknown": true }); + + expect(output).toContain("z.unknown()"); + expect(output).not.toContain("z.any()"); +});