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

如何为已创建的ZodType添加required_error与invalid_type_error属性

给嵌套Zod对象自动添加统一错误提示的工具函数实现

要实现给包含Zod类型的对象自动批量添加required_error和invalid_type_error,核心思路是递归遍历对象结构,利用Zod官方的withErrorMap方法覆盖默认错误提示——既保留原有Schema的所有校验规则,又能统一替换错误文本,同时避免直接修改Zod内部_def属性带来的副作用。

完整代码实现

假设你已经有了错误提示文本的生成逻辑,这里直接整合进工具函数:

import { ZodType, ZodOptional, ZodNullable, ZodArray, ZodObject, ZodUnion, ZodErrorMap } from 'zod';

// 自定义错误提示生成函数(可根据需求修改)
function getRequiredError(fieldPath: string): string {
  return `${fieldPath} 为必填项`;
}

function getInvalidTypeError(fieldPath: string, expectedType: string): string {
  return `${fieldPath} 必须是${expectedType}类型`;
}

function addZodErrors<T extends Record<string, any>>(schema: T, parentPath = ''): T {
  return Object.fromEntries(
    Object.entries(schema).map(([key, val]) => {
      const currentPath = parentPath ? `${parentPath}.${key}` : key;

      // 处理Zod类型
      if (val instanceof ZodType) {
        let innerSchema = val;
        // 先解包可选/可空类型,获取真实的内部Schema
        if (val instanceof ZodOptional || val instanceof ZodNullable) {
          innerSchema = val.unwrap();
        }

        // 映射Zod类型到友好的中文名称
        let expectedType: string;
        switch (innerSchema._def.typeName) {
          case 'ZodString':
            expectedType = '字符串';
            break;
          case 'ZodNumber':
            expectedType = '数字';
            break;
          case 'ZodBoolean':
            expectedType = '布尔值';
            break;
          case 'ZodDate':
            expectedType = '日期';
            break;
          case 'ZodArray':
            expectedType = '数组';
            break;
          case 'ZodObject':
            expectedType = '对象';
            break;
          default:
            expectedType = '有效数据';
        }

        // 自定义错误映射,覆盖指定错误类型的提示
        const customErrorMap: ZodErrorMap = (issue, ctx) => {
          if (issue.code === 'invalid_required') {
            return { message: getRequiredError(currentPath) };
          } else if (issue.code === 'invalid_type') {
            return { message: getInvalidTypeError(currentPath, expectedType) };
          }
          // 其他错误类型保留Zod默认提示
          return ctx.defaultError(issue);
        };

        let newSchema: ZodType;
        // 针对不同Zod类型做递归处理
        if (val instanceof ZodOptional || val instanceof ZodNullable) {
          // 先处理内部Schema,再重新包装为可选/可空
          const processedInner = addZodErrors({ temp: innerSchema }, currentPath).temp as ZodType;
          newSchema = val instanceof ZodOptional ? processedInner.optional() : processedInner.nullable();
        } else if (val instanceof ZodArray) {
          // 递归处理数组元素的Schema
          const processedItem = addZodErrors({ temp: val.element }, `${currentPath}[元素]`).temp as ZodType;
          newSchema = val.element.constructor(processedItem).withErrorMap(customErrorMap);
        } else if (val instanceof ZodObject) {
          // 递归处理对象的所有属性
          const processedShape = addZodErrors(val.shape, currentPath);
          newSchema = val.constructor(processedShape).withErrorMap(customErrorMap);
        } else if (val instanceof ZodUnion) {
          // 递归处理联合类型的每个选项
          const processedOptions = val.options.map(opt => 
            addZodErrors({ temp: opt }, `${currentPath}.联合选项`).temp as ZodType
          );
          newSchema = val.constructor(processedOptions).withErrorMap(customErrorMap);
        } else {
          // 基础类型直接应用错误映射
          newSchema = val.withErrorMap(customErrorMap);
        }

        return [key, newSchema];
      } 
      // 递归处理嵌套的普通对象
      else if (typeof val === 'object' && val !== null) {
        return [key, addZodErrors(val, currentPath)];
      } 
      // 非Zod类型属性直接返回
      else {
        return [key, val];
      }
    })
  ) as T;
}

关键细节说明

  1. 递归遍历与嵌套处理:自动处理对象的多层嵌套结构,包括数组、对象、可选/可空、联合类型等复杂Zod类型。
  2. 错误映射的安全性:使用Zod官方的withErrorMap方法,而非直接修改_def.errorMap——后者会破坏Zod的不可变性设计,可能引发意外问题。
  3. 保留原有校验规则:所有原Schema的校验逻辑(比如z.string().email()的邮箱格式校验、z.number().min(18)的最小值限制)都会被完整保留,仅替换指定类型的错误提示。
  4. 灵活的错误文本生成:getRequiredError和getInvalidTypeError可以根据业务需求自由修改,比如添加多语言支持、调整提示话术。

使用示例

import { z } from 'zod';

// 原始Schema
const originalSchema = {
  username: z.string().email(),
  age: z.number().min(18),
  profile: z.object({
    bio: z.string().optional(),
    avatar: z.string().url()
  }),
  tags: z.array(z.string())
};

// 自动添加错误提示
const schemaWithErrors = addZodErrors(originalSchema);

// 测试验证
schemaWithErrors.username.parse(undefined); // 抛出错误:"username 为必填项"
schemaWithErrors.username.parse(123); // 抛出错误:"username 必须是字符串类型"
schemaWithErrors.age.parse('20'); // 抛出错误:"age 必须是数字类型"
schemaWithErrors.profile.avatar.parse('not-a-url'); // 保留原url格式错误提示:"Invalid url"

扩展建议

  • 如果需要覆盖更多错误类型(比如邮箱格式错误、数字最小值错误),可以在customErrorMap中添加对应issue.code的处理逻辑。
  • 对于枚举、字面量等特殊Zod类型,可以在expectedType的switch分支中添加对应的映射。

内容的提问来源于stack exchange,提问作者docksdocks

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 20:10:38