Type error with discriminatedUnion when using dynamic amount of objects
For a project I am working on we are using zod to type check user provided input at runtime, we have "sections" which are defined in a manifest. I need to have a discriminatedUnion by am getting a type error when doing the following:
const section = z.discriminatedUnion('type', [
...Object.values(manifest.sections).map((section) => section.zod),
])
The above code does work, and what is weirder is that when accessing the items individually it works:
const zodObjects = Object.values(manifest.sections).map((section) => section.zod)
const section = z.discriminatedUnion('type', [
zodObjects[0],
zodObjects[1],
zodObjects[2],
// ...
])
At a guess it's because it doesn't know that it's a array with multiple items but am unsure the best way of fixing this
This issue is probably the same as https://github.com/colinhacks/zod/issues/989, except this issue is actually open (I can't get anyone to re-open the other issue).
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Still relevant
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
🔨
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
🔔
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Boop
👋
Yeah, the issue here is that we enforce at the type level that the array has at least two items ([T, T, ...T[]]), but the type of Object.values is a (potentially empty) array. You can get around this in a few ways:
- Make a function that wraps your
Object.valuescalls and uses a runtime check and assertion that the value has two or more values. - Use some type casting to assert that the
Object.valuesreturns[T, T, ...T[]]to tell the compiler that you are absolutely certain it contains at least two values of typeT. - Throw caution to the wind and use
as any#yolo
import { z } from "zod";
const manifest = {
sections: [
z.object({ type: z.literal("main") }),
z.object({ type: z.literal("nav") }),
z.object({ type: z.literal("footer") })
]
};
function atLeastTwo<T>(arr: T[]): [T, T, ...T[]] {
if (arr.length < 2) {
throw new Error("Instantiated with fewer than two items");
}
return arr as [T, T, ...T[]];
}
const section = z.discriminatedUnion("type", [
...(atLeastTwo(manifest.sections)),
]);
TL;DR: we want to enforce that the array you pass to discriminatedUnion (and a few other constructors!) has at least two values, and the only way to satisfy TypeScript is to actually ensure that statically.