If I had a property that should be limited to only a few possible values, using Yup I could do something like:
prop: Yup.string().oneOf([5, 10, 15])
I can not seem to find something similar in Zod. Of course, I can do that validation in the following way:
const allowedValues = [5, 10, 15];
const schema = z.object({
prop: z.number().superRefine((val, ctx) => {
if (allowedValues.includes(val)) return true;
ctx.addIssue({
message: `${ctx.path} must be one of ${allowedValues}`,
code: "custom",
});
return false;
}),
});
But I was wondering if it could be done by writing less code.
In this particular case you could use z.union
with z.literal
to at least write the code more declaratively:
const schema = z.object({
prop: z.union([z.literal(5), z.literal(10), z.literal(15)]),
});
This approach also has the added benefit of parsing the exact type 5 | 10 | 15
as the result rather than simply number
.
That can get slightly tedious to write out so it might make sense if you find yourself doing this a lot to write a quick helper like:
// This signature might be a bit overkill but these are the types
// z.literal allows.
function oneOf<T extends string | number | boolean | bigint | null | undefined>(
t: readonly [T, T, ...T[]],
) {
// A union must have at least 2 elements so to work with the types it
// must be instantiated in this way.
return z.union([
z.literal(t[0]),
z.literal(t[1]),
// No pointfree here because `z.literal` takes an optional second parameter
...t.slice(2).map(v => z.literal(v)),
])
}
// This will be equivalent to the first schema
const schema2 = z.object({
// This will still typecheck without as const but with weaker
// types than the first example.
prop: oneOf([5,10,15] as const),
});