Search code examples
javascripttypescriptzod

How to use variable as enum array in Zod?


I have this:

const E = [
  '3dostr',
  '3g2', 
  ...
  'xwd_pipe',
  'xwma',
  'yop',
  'yuv4mpegpipe',
]

type FfmpegFormatContentKey = (typeof E)[number]

export const FfmpegFormatContentKeyModel: z.ZodType<FfmpegFormatContentKey> =
  z.enum(E)

At the very bottom:

export const FfmpegFormatContentKeyModel: z.ZodType<FfmpegFormatContentKey> =
  z.enum(E)

That doesn't work, giving this error with no "quick fixes" available:

No overload matches this call.
  Overload 1 of 2, '(values: readonly [string, ...string[]], params?: RawCreateParams): ZodEnum<[string, ...string[]]>', gave the following error.
    Argument of type 'string[]' is not assignable to parameter of type 'readonly [string, ...string[]]'.
      Source provides no match for required element at position 0 in target.
  Overload 2 of 2, '(values: [string, ...string[]], params?: RawCreateParams): ZodEnum<[string, ...string[]]>', gave the following error.
    Argument of type 'string[]' is not assignable to parameter of type '[string, ...string[]]'.
      Source provides no match for required element at position 0 in target.ts(2769)
const E: string[]

It looks like it is expecting the explicit array directly in the input, like this:

export const FfmpegFormatContentKeyModel: z.ZodType<FfmpegFormatContentKey> =
  z.enum(['3dostr', '3g2', '3gp', '4xm', 'a64'])

That works, now. It works just fine putting all those values explicitly/directly in the input for z.enum.

Goal: The goal is to not include these large arrays twice in the final output JS.

  1. Once as an export const MY_ARRAY = [...].
  2. Once as an export const MyArrayModel = z.enum([...]).

How can I do that?

Perhaps there is something like:

export const MY_ARRAY = MyArrayModel.getEnumArray()

Or something like that?

Or perhaps there is some way to format the input to z.enum so it accepts a variable name like z.enum(X)?

I need both forms of the values, array and enum, in the final JS output. The array will be used for things like constructing React <select> options, and the enum will be used for validating method arguments, etc.. So both are needed, but just not sure how to get both without explicitly writing both to the output JS build.

I tried this as well:

export const FfmpegDecoderAudioModel: z.ZodType<FfmpegDecoderAudio> =
  z.enum(X as readonly [string, ...string])

But get a similar error:

Conversion of type 'string[]' to type 'readonly [string, ...any[]]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  Source provides no match for required element at position 0 in target.ts(2352)

This seems to work, but is there a better way?

export const FfmpegDecoderAudioModel: z.ZodType<FfmpegDecoderAudio> =
  z.enum(X as unknown as readonly [string, ...Array<string>])

Solution

  • The zod.enum requires values: readonly [string, ...string[]], which means we have to pass it readonly array with at least one value.

    Your const E is not readonly array. Typescript needs some more.

    You need to state the const E to be deep readonly. you have not protected that no one can programmatically add values to the array.

    The way to do is to use the as const trailing operator.

    const E = [ ... ] as const;
    

    Working example here

    More info on the as const trailing annotation here