使用Zod的superRefine时如何获取Schema的shape属性?
问题解决:Zod使用superRefine后无法内省Schema的shape属性
为什么superRefine会改变Schema类型?
Zod的superRefine方法不会修改原有的ZodObject实例,而是返回一个全新的ZodEffects对象——它相当于一个"包装器",把原Schema和自定义验证逻辑整合在一起。所以原本的z.AnyZodObject类型会变成z.ZodEffects<z.AnyZodObject>,而ZodEffects并没有shape属性,这就是你无法继续内省的原因。
解决方案:提取被包裹的原始Schema
Zod为ZodEffects提供了官方的unwrap()方法,可获取它内部包裹的原始Schema。如果存在多层ZodEffects(比如多次调用superRefine),可以递归解开所有包装,直到拿到底层的ZodObject。
第一步:编写工具函数提取原始对象Schema
import { z } from "zod"; function getBaseObjectSchema(schema: z.ZodTypeAny): z.ZodObject<any> { let currentSchema = schema; // 递归解开所有ZodEffects包装 while (currentSchema instanceof z.ZodEffects) { currentSchema = currentSchema.unwrap(); } // 确保最终拿到的是ZodObject if (!(currentSchema instanceof z.ZodObject)) { throw new Error("传入的Schema并非基于ZodObject"); } return currentSchema; }
第二步:修改handleForm函数适配ZodEffects
把参数类型从z.AnyZodObject放宽到z.ZodTypeAny,先提取原始Schema再进行内省:
import { z } from "zod"; import { React } from "react"; export const handleForm = (schema: z.ZodTypeAny) => (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); const data = new FormData(event.currentTarget); // 获取底层的ZodObject const baseSchema = getBaseObjectSchema(schema); const keys = Object.keys(baseSchema.shape) as Array<keyof z.infer<typeof schema>>; // 基于原始Schema判断数组类型键 const arrayKeys = keys.filter(key => baseSchema.shape[key] instanceof z.ZodArray); const values = Object.fromEntries( Array.from(data.keys()).map(key => [ key, arrayKeys.includes(key as any) ? data.getAll(key) : data.get(key) ]) ); // 后续仍可用原schema验证数据,ZodEffects会包含所有验证逻辑 const validatedData = schema.parse(values); };
关于内部API的说明
你提到的_input、_output、_def属于Zod的内部未公开API,官方不保证这些属性的稳定性,版本更新时可能被修改或移除,因此不建议在生产代码中使用,优先使用官方提供的unwrap()方法。
内容的提问来源于stack exchange,提问作者pom421
相关产品推荐
相关产品推荐

