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

使用泛型结合toZod时无法访问shape属性的类型错误求助

问题

我在创建Zod Schema时希望保证类型安全,因此会基于TypeScript数据类型(比如type Option = { id: number };),借助toZod工具将TS类型转换为Zod Schema类型。

针对单个Option类型编写的createOptionSchema函数可以正常工作,能校验输入的Schema是否符合类型要求。但将其改造为支持任意数据类型的泛型函数createSchema时,出现类型错误:Type 'shape' cannot be used to index type 'toZod<T>'。

我尝试过自定义toZod类型、使用satisfies关键字,但都未能解决问题,需要有效的解决方案。

解决方案

核心原因

错误的本质是:toZod<T>的返回类型并不一定包含shape属性——只有当T是对象类型时,对应的Zod Schema才是ZodObject实例,才会有shape属性。如果不对泛型T做约束,TypeScript无法确定toZod<T>必然包含shape,因此抛出索引错误。

方案1:给泛型添加对象类型约束

通过限定T为对象类型,同时约束toZod<T>为ZodObject,确保可以安全访问shape:

import { ZodObject, z } from "zod";
import { toZod } from "tozod";

// 限定T为对象类型
type ObjectType = Record<string, unknown>;

function createSchema<T extends ObjectType>(
  schema: ZodObject<Extract<toZod<T>, ZodObject<any>>["shape"]>
) {
  return schema;
}

// 使用示例
type Option = { id: number };
const optionSchema = createSchema<Option>(z.object({ id: z.number() })); // 类型校验正常

方案2:使用类型守卫做运行时校验

如果需要支持非对象类型的Schema,可以在函数内部通过类型守卫判断当前Schema是否为ZodObject,再安全访问shape:

import { ZodObject, z } from "zod";
import { toZod } from "tozod";

function createSchema<T>(schema: toZod<T>) {
  // 类型守卫:判断是否为ZodObject实例
  if (schema instanceof z.ZodObject) {
    // 此处可安全访问shape属性
    console.log(schema.shape);
    return schema.shape;
  }
  // 处理非对象类型的Schema
  return schema;
}

// 使用示例
type Option = { id: number };
createSchema<Option>(z.object({ id: z.number() })); // 正常执行
createSchema<string>(z.string()); // 也支持非对象类型

方案3:借助z.infer反向约束

如果已经有现成的Zod Schema,也可以通过z.infer从Schema反向推导TS类型,进一步强化类型安全:

import { z } from "zod";

function createSchema<T>(schema: z.ZodType<T>) {
  return schema;
}

// 使用示例
const optionSchema = createSchema(z.object({ id: z.number() }));
type Option = z.infer<typeof optionSchema>; // 从Schema推导TS类型

内容的提问来源于stack exchange,提问作者Benjamin L.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 17:14:51