Union of Enum values or object values
Is there any way to create a union validation of enum values or object values?
I tried the code below but it doesn't work:
import {z} from 'zod';
const Fruits = {
Apple: "apple",
Banana: "banana",
} as const;
const FruitEnum = z.nativeEnum(Fruits);
type Fruit = z.infer<typeof FruitEnum>; // "apple" | "banana"
const fruitValuesLiterals = Object.values(Fruits).map(value => z.literal(value)); // z.ZodLiteral<"apple" | "banana">[]
const fruitUnion = z.union(fruitValuesLiterals); // ERROR: Argument of type 'ZodLiteral<"apple" | "banana">[]' is not assignable to parameter of type 'readonly [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]'.
Same issue here. I'm trying to get keys from an object and my Object.keys(DICT).map(value => z.literal(value)); throws the same error. The as const doesn't help either.
A similar issue happened in discriminatedUnion. Seems that TS engine cannot deduce the generic types from an identical array constructed with array in the types: [...]`.
Current workaround: I expanded the loop since it's not so long.
/*
what i expected:
const fruitUnion = z.union( Object.values(Fruits).map(value => z.literal(value) );
or even better:
const fruitValuesLiterals = Object.values(Fruits).map(value => z.literal(value));
const fruitUnion = z.union(fruitValuesLiterals);
*/
// the work-around:
const fruitUnion = z.union([z.literal('apple'), z.literal('banana')]);
It compiles OK if you create the union and the litterals in a single line, e.g.
const fruitUnion = z.union([z.literal('apple'), z.literal('banana')]); // type: z.ZodUnion<[z.ZodLiteral<"apple">, z.ZodLiteral<"banana">]>
However as soon as you try and split the creation of the litterals and union into multiple steps, it cant compile:
const fruitValuesLiterals = [z.literal('apple'), z.literal('banana')]; // type: (z.ZodLiteral<"apple"> | z.ZodLiteral<"banana">)[]
const fruitUnion = z.union(fruitValuesLiterals); // Argument of type '(ZodLiteral<"apple"> | ZodLiteral<"banana">)[]' is not assignable to parameter of type 'readonly [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]'. Source provides no match for required element at position 0 in target.
Based on this, I'm thinking that it's not currently possible to achieve this. However It would be good to see this in the future.
Just to expand on my previous comment, it does compile if you specify the type as a tuple with ZodLitteral types, e.g:
const fruitValuesLiterals: [z.ZodLiteral<'apple'>, z.ZodLiteral<'banana'>] = [z.literal('apple'), z.literal('banana')]; // type: (z.ZodLiteral<"apple"> | z.ZodLiteral<"banana">)[]
const fruitUnion = z.union(fruitValuesLiterals); // type: z.ZodUnion<[z.ZodLiteral<"apple">, z.ZodLiteral<"banana">]>
However I'm not sure of any way to build this type dynamically based on the values in the enum/const object.
@devklick I think you are missing the point here:
What I wanted was to use iteration with zod and something like Template Literal Types.
The point of this issue is to say that it only works with z.union([z.literal('apple'), z.literal('banana')]) but not with z.union(Object.values(Fruits).map(value => z.literal(value))). You are reiterating that in the first case it works and didn't mention the second case at all. (Tho AFAIK it is hard to achieve in TS.)
@PabloLION Thanks for clarifying. Can you elaborate on your workaround? Not sure what you mean by expanding the loop.
@devklick Sorry for the vagueness. I edited the post before. It's just like what you did.
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.
Hi @colinhacks , would you mind checking this issue and decide if we are going to work on it? Thanks in advance.
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.