zodでobjectのkeyからenumを定義する(javascript)

JavaScript

{ JP; ‘日本語’, EN: ‘英語’ }のような内部値をユーザ表示用の文字列に変換するオブジェクトや辞書を持っているケースは多いと思います。
zodのenumを利用するにはz.enum([‘JP’, ‘EN’])のようなarrayで渡す必要があるため、変換用オブジェクトの他に配列を定義する必要があります。

nativeEnumというものもあり、こちらを利用する場合はkeyではなくvalueのenumとして扱われるため以下のようにする必要があります。この場合、データ=>表示用文字列に変換するオブジェクトを別途持つ必要があるため二重定義になり不毛です。key-valueをswapする変換用の関数定義してもいいですが表示のたびにobjectをswapするのは無駄が多い気がします。

const dictionary = { '日本語':'JP', '英語':'EN' } as const
z.nativeEnum(dictionary)

ということで調べてみたところ、こう書くのが良さそうです。

export function zodEnumFromObjKeys<T extends string>(
  obj: Record<T, unknown>,
): z.ZodEnum<[T, ...T[]]> {
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
  const [firstKey, ...otherKeys] = Object.keys(obj) as T[]
  if (typeof firstKey !== "string") throw new Error("key is not string")
  return z.enum([firstKey, ...otherKeys])
}

利用する場合はこのようになります。

const myObject = {
  key1: "value1",
  key2: "value2",
} as const

const mySchema = zodEnumFromObjKeys(myObject)
type MyType = z.infer<typeof mySchema>
const example: MyType = "key1"
const example2: MyType = "key3" // type error

参考

Enum From Object Literal Keys · colinhacks zod · Discussion #839
Asdiscussedinotherissuesinthisrepo,itisnottrivialtocreateanenumfromanobjectliteralkeys,orevenfromanarrayofstrings:(Argumentoftype'K[]'isnotassignabletoparameter...

コメント