Open mildfuzz opened 3 months ago
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:
z.literal
to make it a little easier:
const l = z.literal;
z.custom
schema 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 {
}
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
// 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 — 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: @.***>
Trying to something like this, just to reduce noise of lots of potential literal values:
but right now,
UnionType
resolves toany