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

在Zod区分联合中使用refine校验字段报错,如何实现日期校验?

Zod区分联合类型中添加跨字段日期校验的解决方案

问题描述

我定义了如下Zod区分联合类型:

enum Option {
    FULL = 'FULL',
    TIME_PERIOD = 'TIME_PERIOD',
    MONTH = 'MONTH',
}

const schema = z.discriminatedUnion('option', [
  z.object({ option: z.literal(Option.FULL) }),
  z.object({
    option: z.literal(Option.TIME_PERIOD),
    from: z.date(),
    to: z.date(),
  }),
  z.object({ option: z.literal(Option.MONTH), month: z.date() }),
])

想为TIME_PERIOD选项的对象添加校验,确保from日期早于to日期,修改后代码如下:

const schema = z.discriminatedUnion('option', [
  z.object({ option: z.literal(Option.FULL) }),
  z.object({
    option: z.literal(Option.TIME_PERIOD),
    from: z.date(),
    to: z.date(),
  }).refine(
    ({ from, to }) => isBefore(from, to),
    {
        message: "\"from\" must be before \"to\"",
        path: ['from'],
    }
  ),
  z.object({ option: z.literal(Option.MONTH), month: z.date() }),
])

但出现错误提示:"type ZodEffects is not allowed as ZodDiscriminatedUnionOption"。


解决方案

Zod的discriminatedUnion要求每个成员必须是原始ZodObject类型,不能是经refine包装后的ZodEffects类型,可通过以下两种方式实现需求:

方法1:使用superRefine内联校验

直接在TIME_PERIOD对应的对象schema内部用superRefine添加跨字段校验,不会改变schema的原始类型,符合区分联合的要求:

enum Option {
    FULL = 'FULL',
    TIME_PERIOD = 'TIME_PERIOD',
    MONTH = 'MONTH',
}

const schema = z.discriminatedUnion('option', [
  z.object({ option: z.literal(Option.FULL) }),
  z.object({
    option: z.literal(Option.TIME_PERIOD),
    from: z.date(),
    to: z.date(),
  }).superRefine(({ from, to }, ctx) => {
    if (!isBefore(from, to)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: "\"from\" must be before \"to\"",
        path: ['from'],
      });
    }
  }),
  z.object({ option: z.literal(Option.MONTH), month: z.date() }),
])

方法2:改用普通z.union类型

如果坚持使用refine,可以先单独定义带校验的子schema,再用普通z.union替代discriminatedUnion(注意普通联合类型的性能略低于区分联合,但逻辑效果一致):

enum Option {
    FULL = 'FULL',
    TIME_PERIOD = 'TIME_PERIOD',
    MONTH = 'MONTH',
}

const fullSchema = z.object({ option: z.literal(Option.FULL) });
const timePeriodSchema = z.object({
  option: z.literal(Option.TIME_PERIOD),
  from: z.date(),
  to: z.date(),
}).refine(
  ({ from, to }) => isBefore(from, to),
  {
      message: "\"from\" must be before \"to\"",
      path: ['from'],
  }
);
const monthSchema = z.object({ option: z.literal(Option.MONTH), month: z.date() });

const schema = z.union([fullSchema, timePeriodSchema, monthSchema]);

核心原因

discriminatedUnion是Zod针对带判别字段的联合类型做的优化,它依赖每个分支的原始对象schema来快速匹配判别字段,因此不接受被refine包装后的ZodEffects类型。而superRefine是直接在原始对象schema上扩展校验逻辑,不会改变schema的类型,因此可以正常用于区分联合中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 23:20:27