typescriptvalidationzod

Coerce string to literal number with Zod


I have a property that is supposed to allow only certain numbers:

{
   ...
   x: 1 | -1
}

How to define this in the input validation schema?

If input is JSON it's easy:

x: z.union([z.literal(-1), z.literal(1)])

but if input comes in the search query, then values are strings, so I need to be able to coerce to number, but still limit to -1 and 1 to make the inferred type from the schema compatible with the TypeScript type.


Solution

  • One way to do that would be to accept both numbers and strings, transform strings to number then refine the result to parse only allowed numbers:

    // union of
    z.union([
      z.number(), // number or
      z.string().transform(str => parseInt(str, 10)) // string transformed to number
    ]).refine(v => [1,-1].includes(v)); // refined to allow a set of numbers
    

    or alternatively pipe the transformation to your original schema:

    // union of
    z.union([
      z.number(), // number or
      z.string().transform(str => parseInt(str, 10)) // string transformed to number
    ]).pipe(z.union([z.literal(1), z.literal(-1)]))
    

    Or you can use a preprocess also followed by a pipe:

    z.preprocess(
        v => typeof v === 'number' ? v : parseInt(v, 10), // preprocessing
        z.number() // as number
    ).pipe(z.union([z.literal(1), z.literal(-1)]));
    

    The docs also point to coerce which can work the same way and matches more closely your original question albeit with less control on the conversion:

    // coerce internally uses Number(value)
    z.coerce.number().pipe(z.union([z.literal(1), z.literal(-1)]));
    

    The resulting type should correctly be inferred as number in all cases and probably as the required union in the pipe cases. Just choose your favorite flavor and control level :)

    zod playground