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.
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 :)