Search code examples
typescriptzod

Validate field in discriminated union based on other field in zod


I have the following discriminated union:

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() }),
])

Now I want to refine the second object, to check if the date from is before 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() }),
])

But this gives me an error, basically saying "type ZodEffects is not allowed as ZodDiscriminatedUnionOption".

How can I achieve the desired behaivior in validation?

Here is a codesanbox of my problem: https://codesandbox.io/s/zod-refine-in-discriminated-union-5kve15


Solution

  • The solution is to refine the discriminated union itself, not the option inside the union:

    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() }),
    ]).refine(
      (data) => {
        if (data.option === Option.TIME_PERIOD) {
          return isBefore(data.from, data.to)
        }
        return true
      },
      {
        message: '"from" must be before "to"',
        path: ['from'],
      }
    )