Search code examples
typescriptzod

Zod nativeEnum type checks enum's value


I am using a zod schema to validate an object with an enum field in it:

enum Colour {
    red: 'Red',
    blue: 'Blue',
}

const schema = z.object({
    colour: z.nativeEnum(Colour),
});

I have input data coming from an api for the colour values as either 'red' or 'blue', and I want to check this with the above schema. However, the above schema's nativeEnum checks according the capitalized cases in the enum, not the enum properties:

enum Colour {
    red: 'Red',
    blue: 'Blue',
}

const schema = z.object({
    colour: z.nativeEnum(Colour),
});

const rawInput1 = {
    colour: 'red' // should be identified as valid
};
const parsedInput1 = schema.parse(rawInput1); // this fails

const rawInput2 = {
    colour: 'Red' // should be identified as invalid
};
const parsedInput2 = schema.parse(rawInput2); // this passes

How can I make zod validate based the property in the enum instead of the value? And why is this happening?

The reason why I also want to parse the enum properties and I have defined the enum that way is because I want to parse the object and use the colour variable to index its string value in the enum: Colour[parsedInput1.colour]. This will not be possible if colour is the string value.


Solution

  • You could get it to validate based on the keys using a regular .enum() and extracting the enum keys with Object.keys():

    enum Colour {
        red = 'Red',
        blue = 'Blue',
    }
    
    const keys = Object.keys(Colours) // ["red", "blue"]
    
    const schema = z.object({
        colour: z.enum(keys),
    });
    

    But the issue is that the type of keys here is string[] and not ["red", "blue"] (even though this will be the actual value at runtime). So it will work, but you won't get the typescript validation...

    With keyof typeof it is possible to obtain a unions of the enum's keys

    type KeyUnion = keyof typeof Colour // "red" | "blue"
    

    So with a little bit of "lying to typescript" we can achieve the desired result by casting the keys type:

    const keys = Object.keys(Colours) as [keyof typeof Colour]
    const schema = z.object({
        colour: z.enum(keys),
    });
    

    This gets you something that works both at the type level and at runtime.

    However do note that keys is not actually of type [keyof typeof Colour], this is just a lie we tell typescript to obtain the correct inference.