Use arrays to create unions
Trying to something like this, just to reduce noise of lots of potential literal values:
const unionSchema = z.union(([0,2,5,7]).map(i =>z.literal(i)))
type UnionType = z.infer<typeof unionSchema>
but right now, UnionType resolves to any
Yeah, you cannot do dynamic things (like call map) to schemas because the types won't match up. This is just a little bit of safety that the TypeScript compiler provides since at runtime the compiler doesn't keep track of how you're updating your as const array.
I definitely get wanting to improve the ergonomics here to avoid having to wrap each value in a literal but you have a few other options:
- Use an alias for
z.literalto make it a little easier:const l = z.literal; - Use a
z.customschema and provide the type explicitly:const unionLiterals = [0, 2, 5, 7] as const; const unionSchema = z.custom<(typeof unionLiterals)[number]>((v) => unionLiterals.includes(v), ); type UnionType = z.infer<typeof unionSchema>;
If it were me, I'd either just type out the z.literal(val) syntax and just chalk it up to typing practice 😅 or use the custom schema if it's a really large array that I was copying from somewhere else (like an array of all locales, or country names, or something like that).
While TypeScript doesn't support preserving tuple types when using Array.prototype.map (see https://github.com/microsoft/TypeScript/issues/29841), for this case we could define our own mapping function. I think something this could work for your initial question:
function zodLiteralUnion<T extends readonly [z.Primitive, z.Primitive, ...z.Primitive[]]>(
primitives: [...T]
) {
const literals = primitives.map(x => z.literal(x)) as {
[Index in keyof T]: z.ZodLiteral<T[Index]>
}
return z.union(literals)
}
// const A: z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>
const A = z.union([z.literal(1), z.literal(2), z.literal(3)])
// type A = 2 | 1 | 3
type A = z.infer<typeof A>
// const B: z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>
const B = zodLiteralUnion([1, 2, 3])
// type B = 2 | 1 | 3
type B = z.infer<typeof B>
Ooh, that's nice
On Fri, 19 Jul 2024, 07:26 Damjan Polugic, @.***> wrote:
While TypeScript doesn't support preserving tuple types when using Array.prototype.map (see microsoft/TypeScript#29841 https://github.com/microsoft/TypeScript/issues/29841), for this case we could define our own mapping function. I think something this could work for your initial question:
function zodLiteralUnion<T extends readonly [z.Primitive, z.Primitive, ...z.Primitive[]]>( primitives: [...T]) { const literals = primitives.map(x => z.literal(x)) as { [Index in keyof T]: z.ZodLiteral<T[Index]> }
return z.union(literals)} // const A: z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>const A = z.union([z.literal(1), z.literal(2), z.literal(3)])// type A = 2 | 1 | 3type A = z.infer<typeof A> // const B: z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>const B = zodLiteralUnion([1, 2, 3])// type B = 2 | 1 | 3type B = z.infer<typeof B>
— Reply to this email directly, view it on GitHub https://github.com/colinhacks/zod/issues/3651#issuecomment-2238385279, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABQAYOLSLYIGUHECNFQ6OLZNCWSJAVCNFSM6AAAAABLCW4GRWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMZYGM4DKMRXHE . You are receiving this because you authored the thread.Message ID: @.***>
Hi, @mildfuzz. I'm Dosu, and I'm helping the Zod team manage their backlog. I'm marking this issue as stale.
Issue Summary:
- You raised an issue about using arrays to create union types with
z.literal, which resulted in unexpected type inference toany. - @scotttrinh explained the limitations of TypeScript with dynamic operations like
mapon schemas and suggested alternatives. - @dpolugic proposed a custom mapping function to preserve tuple types, which you appreciated.
- The discussion provided practical solutions, such as using a custom schema or alias for
z.literal, addressing the issue.
Next Steps:
- Please let me know if this issue is still relevant to the latest version of the Zod repository. If so, you can keep the discussion open by commenting on the issue.
- Otherwise, the issue will be automatically closed in 7 days.
Thank you for your understanding and contribution!