zod icon indicating copy to clipboard operation
zod copied to clipboard

Union of Enum values or object values

Open simonepizzamiglio opened this issue 3 years ago • 7 comments

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[]]'.

TS playground code

simonepizzamiglio avatar May 17 '22 14:05 simonepizzamiglio

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.

PabloLION avatar Jul 07 '22 07:07 PabloLION

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')]);

PabloLION avatar Jul 07 '22 09:07 PabloLION

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.

devklick avatar Aug 23 '22 10:08 devklick

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 avatar Aug 23 '22 11:08 devklick

@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 avatar Aug 24 '22 04:08 PabloLION

@PabloLION Thanks for clarifying. Can you elaborate on your workaround? Not sure what you mean by expanding the loop.

devklick avatar Aug 24 '22 06:08 devklick

@devklick Sorry for the vagueness. I edited the post before. It's just like what you did.

PabloLION avatar Aug 25 '22 11:08 PabloLION

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.

stale[bot] avatar Oct 24 '22 12:10 stale[bot]

Hi @colinhacks , would you mind checking this issue and decide if we are going to work on it? Thanks in advance.

PabloLION avatar Oct 24 '22 15:10 PabloLION

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.

stale[bot] avatar Jan 22 '23 20:01 stale[bot]