You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 14:53:14