Search code examples
typescriptzod

How to make a zod type of string but with possible static values


I need to make a property accept a predefined set of string literals, but it can also accept any string values.

This is how I did it in typescript

const PREDEFINED_VALUES = ['value1', 'value2', 'value3'] as const;

type TStaticValues = typeof PREDEFINED_VALUES[number]
type Values = (string & Record<string, unknown>) | TStaticValues;

With this, I can benefit from intellisense of predefined values to check for. I tried to do it in zod with the following:

const type = z.object({
  // some other types
  stringWithPredefinedValues: z.union([
    z.string().and(z.object({})),
    z.enum(PREDEFINED_VALUES)
  ])
})

But when I try to check for predefined values, it shows a ts error.

const testValue: string = 'any string should be accepted, but predefined values get detected.';

const check = PREDEFINED_VALUES.includes(testValue)
// ts error: Type 'string & {}' is not assignable to type '"value1" | "value2" | "value3"'.

I am uncertain if my zod object is correct, or if there is some sort of limitation.


Solution

  • You were pretty close to the solution. The missing part was re-assigning the array's type to be closer to string[]. Enum arrays are restricted in terms of array operations whenever they invoke generic functions.

    Playground

    type S<T> = T extends string ? T | string & {} : never;
    
    const PREDEFINED_VALUES = ['value1', 'value2', 'value3'] as const;
    
    // This is what you want
    const USABLE_ARRAY: readonly S<typeof PREDEFINED_VALUES[number]>[] = PREDEFINED_VALUES;
    
    const testValue: string = 'any string should be accepted, but predefined values get detected.';
    
    const check = USABLE_ARRAY.includes(testValue);
    const check2 = USABLE_ARRAY.includes("");
    

    And intellisense works:

    enter image description here

    As a sidenote, zod will never give you types which you can't easily write by hand.