在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
相关产品推荐
相关产品推荐

