Search code examples
typescriptzod

Require none or both fields with Zod


I have the properties startDate and endDate in a Zod schema. I'd like to verify that either:

  • None of them are set
  • Both of them are set

I.e. if only startDate or only endDate is set, parsing will fail.

The schema looks like:

export const MediumSchema = z.object({
    ImageSetID: z.number().int().positive(),
    ...
    CampaignStartDate: z.date().nullable(),
    CampaignEndDate: z.date().nullable(),
    Url: z.string().url().transform((url) => new URL(url)),
    CDNUrl: z.string().url().transform((url) => new URL(url))
});

How do I achieve this?


Solution

  • You can use union or the built in or method to union the two possible schemas you expect. For example:

    import { z } from 'zod';
    
    const schema = z.object({
      startDate: z.date(),
      endDate: z.date(),
    });
    
    const schemaBothUndefined = z.object({
      startDate: z.undefined(),
      endDate: z.undefined(),
    });
    
    const bothOrNeither = schema.or(schemaBothUndefined);
    
    console.log(bothOrNeither.safeParse({})); // success
    console.log(bothOrNeither.safeParse({
      startDate: new Date(),
      endDate: new Date(),
    })); // success
    console.log(bothOrNeither.safeParse({
      startDate: new Date(),
    })); // failure
    

    Edit: Further details when part of a larger schema

    If the schema is to be used as part of a larger schema you can use and for those fields like:

    import { z } from "zod";
    
    const startAndEnd = z.object({
      CampaignStartDate: z.date(),
      CampaignEndDate: z.date(),
    });
    const neitherStartNorEnd = z.object({
      CampaignStartDate: z.undefined(),
      CampaignEndDate: z.undefined(),
    });
    
    const CampaignDates = startAndEnd.or(neitherStartNorEnd);
    
    export const MediumSchema = z.object({
      id: z.string(),
    }).and(CampaignDates);
    
    console.log(MediumSchema.safeParse({ id: '11' })); // success 
    console.log(MediumSchema.safeParse({ 
      id: '11',
      CampaignStartDate: new Date(),
      CampaignEndDate: new Date()
    })); // Success
    console.log(MediumSchema.safeParse({ 
      id: '11',
      CampaignEndDate: new Date()
    })); // Failure