递归遍历Zod Schema生成字段必填状态映射的类型定义问题
递归遍历Zod Schema生成字段必填状态映射的类型定义问题
我完全懂你的需求——你需要一个工具能递归遍历Zod Schema,生成和Schema结构完全一致的对象,标记每个字段是必填还是可选,这样就能在React Hook Form里给用户明确展示字段状态对吧?你的递归函数逻辑已经跑通了,核心问题就是TypeScript的类型定义没跟上,我来帮你修正这个问题!
核心问题分析
你之前的RequiredFieldsMap类型没有准确区分嵌套的ZodObject和普通Zod类型,导致递归后的类型推断出错。咱们需要调整类型定义,让它能递归识别每个字段的类型,正确生成布尔值或嵌套映射。
修正后的完整代码
首先导入必要的Zod类型,然后定义辅助类型和递归函数:
import { ZodObject, ZodType, ZodOptional } from "zod"; // 辅助类型:判断一个Zod类型是否被标记为可选 type IsZodOptional<T extends ZodType> = T extends ZodOptional<infer _U> ? true : false; // 核心递归类型:生成和Zod Schema结构一致的必填状态映射 type RequiredFieldsMap<T extends Record<string, ZodType>> = { [K in keyof T]: // 如果当前字段是ZodObject,就递归生成嵌套的映射 T[K] extends ZodObject<infer U> ? RequiredFieldsMap<U> // 否则直接返回是否必填的布尔值 : !IsZodOptional<T[K]>; }; // 工具函数:判断单个Zod字段是否可选(逻辑和你之前的一致) function isOptional(schema: ZodType): boolean { return schema.safeParse(undefined).success; } // 递归生成必填字段映射的函数,修正类型定义 export function getRequiredFields<T extends Record<string, ZodType>>( schema: ZodObject<T> ): RequiredFieldsMap<T> { const { shape } = schema; const map = {} as RequiredFieldsMap<T>; for (const key in shape) { const field = shape[key]; if (field instanceof ZodObject) { // 递归处理嵌套的ZodObject,添加类型断言确保类型匹配 map[key] = getRequiredFields(field) as RequiredFieldsMap< T[key] extends ZodObject<infer U> ? U : never >; } else { // 普通字段直接判断是否可选,取反得到必填状态 map[key] = !isOptional(field) as !IsZodOptional<T[key]>; } } return map; }
测试验证
用你提供的示例Schema测试一下,类型推断会完全符合预期:
import { z } from "zod"; const schema = z.object({ name: z.string(), email: z.email().optional(), address: z.object({ line1: z.string(), line2: z.string().optional(), }), }); const requiredMap = getRequiredFields(schema); // 此时requiredMap的类型会被正确推断为: // { // name: true; // email: false; // address: { // line1: true; // line2: false; // }; // }
替代方案(可选)
如果不想自己维护递归逻辑,也可以在定义Zod Schema时给字段添加元数据,再通过遍历提取状态。不过这种方式需要手动维护元数据,不如递归方式自动化:
// 定义Schema时添加必填状态元数据 const schema = z.object({ name: z.string().meta({ required: true }), email: z.email().optional().meta({ required: false }), address: z.object({ line1: z.string().meta({ required: true }), line2: z.string().optional().meta({ required: false }), }), }); // 提取元数据生成映射的函数 function getRequiredFieldsFromMeta<T extends Record<string, ZodType>>(schema: ZodObject<T>) { const map = {} as RequiredFieldsMap<T>; for (const key in schema.shape) { const field = schema.shape[key]; if (field instanceof ZodObject) { map[key] = getRequiredFieldsFromMeta(field); } else { map[key] = field._def.metadata?.[0]?.required ?? !isOptional(field); } } return map; }
不过还是推荐第一种递归方式,不需要额外修改Schema定义,更省心。
内容来源于stack exchange
相关产品推荐
相关产品推荐

