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

如何在Zod中结合superRefine实现带动态验证的判别联合

解决方案:分离公共字段与公共校验,在扩展后统一应用

核心问题是superRefine会将Zod Object转换为Zod Effect类型,而Effect不支持merge等Object Schema的扩展方法。解决思路是让Base Schema保持纯Object类型,将公共校验逻辑抽离,在扩展完成后再应用校验。

方法1:抽离公共校验函数,扩展后追加校验

先定义仅包含公共字段的Base Schema,再把公共复杂校验写成独立函数,最后在扩展后的Schema上调用superRefine应用公共校验,再添加动态校验逻辑。

import { z } from "zod";

// 1. 定义纯Object类型的Base Schema(无superRefine/refine)
const baseSchema = z.object({
  id: z.string().uuid(),
  createdAt: z.date(),
  status: z.enum(["active", "inactive"]),
});

// 2. 抽离公共复杂校验函数
const commonValidation = (data: z.infer<typeof baseSchema>, ctx: z.RefinementCtx) => {
  // 示例:公共跨字段校验 - 已失效的条目不能有未来的创建时间
  if (data.status === "inactive" && data.createdAt > new Date()) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "已失效条目的创建时间不能晚于当前时间",
      path: ["createdAt"],
    });
  }
};

// 3. 动态扩展Schema的工厂函数
const createDynamicSchema = (externalFlag: boolean) => {
  // 先扩展Base Schema,添加业务字段
  const extendedSchema = baseSchema.merge(z.object({
    code: z.enum(["A", "B", "C"]),
    payload: z.record(z.string(), z.any()),
  }));

  // 先应用公共校验,再添加基于code和外部flag的动态校验
  return extendedSchema
    .superRefine(commonValidation)
    .superRefine((data, ctx) => {
      // 动态校验逻辑示例
      switch (data.code) {
        case "A":
          if (!externalFlag && !data.payload["requiredKey"]) {
            ctx.addIssue({
              code: z.ZodIssueCode.custom,
              message: "code为A且外部flag关闭时,payload必须包含requiredKey",
              path: ["payload", "requiredKey"],
            });
          }
          break;
        case "B":
          if (externalFlag && data.payload["maxValue"] > 100) {
            ctx.addIssue({
              code: z.ZodIssueCode.too_big,
              maximum: 100,
              inclusive: true,
              path: ["payload", "maxValue"],
              message: "code为B且外部flag开启时,maxValue不能超过100",
            });
          }
          break;
      }
    });
};

// 使用示例
const schemaForFlagOn = createDynamicSchema(true);
const result = schemaForFlagOn.safeParse({
  id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  createdAt: new Date(),
  status: "active",
  code: "B",
  payload: { maxValue: 90 },
});

方法2:将公共校验封装为独立Schema,使用pipe串联

如果公共校验逻辑复杂,可以将其封装为独立的Zod Effect Schema,再通过pipe方法与扩展后的Object Schema串联,同样能实现保留公共校验+动态扩展的效果。

import { z } from "zod";

const baseSchema = z.object({
  id: z.string().uuid(),
  createdAt: z.date(),
});

// 封装公共校验为独立的Effect Schema
const commonValidationSchema = z.superRefine((data: z.infer<typeof baseSchema>, ctx) => {
  if (data.createdAt > new Date()) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "创建时间不能晚于当前时间",
    });
  }
});

const createDynamicSchema = (externalFlag: boolean) => {
  const extendedSchema = baseSchema.merge(z.object({
    code: z.enum(["X", "Y"]),
    count: z.number(),
  }));

  // 先串联公共校验,再添加动态校验
  return extendedSchema
    .pipe(commonValidationSchema)
    .superRefine((data, ctx) => {
      if (data.code === "X" && externalFlag && data.count < 5) {
        ctx.addIssue({
          code: z.ZodIssueCode.too_small,
          minimum: 5,
          inclusive: true,
          path: ["count"],
          message: "code为X且外部flag开启时,count不能小于5",
        });
      }
    });
};

关键思路总结

  • 让Base Schema保持纯ZodObject类型,只包含字段定义和简单校验(如uuid()、enum()),避免过早使用superRefine/refine转换为Effect类型。
  • 公共复杂校验逻辑抽离为独立函数或Schema,在扩展完成后再应用到Schema上。
  • 通过工厂函数接收外部动态参数(如externalFlag),在内部完成扩展、公共校验、动态校验的串联。

内容的提问来源于stack exchange,提问作者Cristian Flórez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 14:05:17