使用Type Generics构建Zod Schema时遇类型报错问题
Zod泛型函数中
result属性不存在的类型报错问题 问题背景
我正在尝试编写一个接收Zod Schema并生成新Zod Schema的泛型函数,代码如下:
import { z } from 'zod'; export function createResponseSchema<R extends z.ZodTypeAny>( resourceSchema: R ) { return z.object({ result: resourceSchema, code: z.string(), message: z.string(), }); } export function useResponseSchema<R extends z.ZodTypeAny>(resourceSchema: R) { return createResponseSchema(resourceSchema).parse({ code: '200', message: 'OK', result: [{test: 'test'}], }).result; // ^? TypeScript报错:result属性不存在 }
报错信息
Property 'result' does not exist on type '{ [k in keyof baseObjectOutputType<{ result: R; code: ZodString; message: ZodString; }>]: baseObjectOutputType<{ result: R; code: ZodString; message: ZodString; }>[k]; }'.ts(2339)
奇怪的现象
当给resourceSchema添加.array()后,报错竟然消失了:
export function useResponseSchema<R extends z.ZodTypeAny>(resourceSchema: R) { return createResponseSchema(resourceSchema.array()).parse({ code: '200', message: 'OK', result: [{test: 'test'}], }).result; // ^? 无任何报错 }
但我并不需要将Schema转为数组,不理解为什么会出现这种差异。附上我的tsconfig.json配置:
{ "extends": "./node_modules/gts/tsconfig-google.json", "compilerOptions": { "rootDir": ".", "outDir": "build", "target": "es5", "module": "es2020", "moduleResolution": "node", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitAny": true, "skipLibCheck": true }, "include": ["src/**/*.ts", "test/**/*.ts"], "ts-node": { "transpileOnly": true, "files": true, "experimentalResolver": true } }
问题原因
这是Zod泛型类型推断的边界情况导致的:
- 当直接传入
R extends z.ZodTypeAny的泛型参数时,Zod内部的baseObjectOutputType无法明确解析出对象的固定结构,TypeScript只能将返回类型推断为动态的键值映射,而非明确包含result/code/message的对象。 - 调用
.array()后,R被包装成了ZodArray类型,Zod对数组类型的输出推断逻辑更明确,TypeScript能直接识别返回对象中的result属性。
解决方案
方案1:给createResponseSchema添加明确的返回类型标注
直接指定函数返回的Zod对象类型,让TypeScript跳过模糊的泛型推断:
import { z, ZodTypeAny, ZodObject } from 'zod'; export function createResponseSchema<R extends ZodTypeAny>( resourceSchema: R ): ZodObject<{ result: R; code: typeof z.string()._def; message: typeof z.string()._def; }> { return z.object({ result: resourceSchema, code: z.string(), message: z.string(), }) as ReturnType<typeof createResponseSchema<R>>; }
方案2:使用z.infer辅助类型推断
在useResponseSchema中,通过z.infer明确获取解析后的对象类型:
export function useResponseSchema<R extends z.ZodTypeAny>(resourceSchema: R) { const responseSchema = createResponseSchema(resourceSchema); type ResponseData = z.infer<typeof responseSchema>; const parsed = responseSchema.parse({ code: '200', message: 'OK', result: [{test: 'test'}], }) as ResponseData; return parsed.result; }
方案3:简化的类型断言写法
如果觉得上面的写法繁琐,可以直接对解析结果做类型断言:
export function useResponseSchema<R extends z.ZodTypeAny>(resourceSchema: R) { return (createResponseSchema(resourceSchema).parse({ code: '200', message: 'OK', result: [{test: 'test'}], }) as { result: z.infer<R>; code: string; message: string }).result; }
内容的提问来源于stack exchange,提问作者Tracer482
相关产品推荐
相关产品推荐

