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

递归遍历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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 09:10:29