Echo
Echo

Reputation: 631

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.

Upvotes: 2

Views: 3338

Answers (1)

Slava Knyazev
Slava Knyazev

Reputation: 6081

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.

Upvotes: 0

Related Questions