【TypeScript】オブジェクトの Value から Key を取得する

12134 ワード

はじめに

定数オブジェクトのvalueからkey名を取得したい場面があって、定数オブジェクトに対して汎用的に利用できる関数を作成してみた。

こんな書き方もありそう、ここあんまり良くない実装になっているなどご指摘等々あれば気軽にコメントいただけると嬉しいです。

作成した関数

const getKeyByValue = <
  OBJECT extends Record<string | number, any>,
  KEY extends keyof OBJECT,
  VALUE extends OBJECT[KEY]
>(
  obj: OBJECT,
  val: VALUE
): KEY => {
  // key と value を逆にしたオブジェクトを生成
  const keyValueReversedObj: Record<VALUE, KEY> = Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [value, key])
  );

  // そのオブジェクトから key(第2引数の val)に応じた値を取得
  return keyValueReversedObj[val];
};

ジェネリクスを利用していてかなり読みづらくなってしまっているものの、やっていることとしては

  • keyvalueを逆にしたオブジェクトを生成
  • そのオブジェクトからkey(第2引数のval)に応じた値を取得

しているだけである。

解説

こんなユーザーの状態を表す定数をサンプルとして考えてみる。

const UserStatusValue = {
  non: 0,
  init:1,
  active: 2,
} as const;
// 出力結果はこうなる
const key = getKeyByValue(UserStatusValue, 2);
console.log(key); // => active

Genericsの部分

// 第一引数に取れる obj は { key: value, ... } の形式のオブジェクトに限定する
OBJECT extends Record<string | number, any>,

// 返り値は OBJECT の key に限定する
// UserStatusValue の場合 "non" | "init" | "active" となる
KEY extends keyof OBJECT,

// 第二引数に取れる val は第一引数の value に限定する
// UserStatusValue の場合 0 | 1 | 2 となる
VALUE extends OBJECT[KEY]

第一引数をセットした時点で、第二引数に取れる値が限定されるためUserStatusValueに存在しないvalueを第二引数に指定するとエラーになる

// Argument of type '3' is not assignable to parameter of type '0 | 1 | 2'.
const key = getKeyByValue(UserStatusValue, 3);

getKeyByValue() を利用しない場合はこんな形になりそう

getKeyByValue()を利用しない場合、各定数オブジェクトに対してその都度、こんなswitch関数を作成していく必要があると思う。

// こんな関数を各オブジェクトごとに書く必要がある
export const getKeyUserStatus = (
  statusVal: typeof UserStatusValue[keyof typeof UserStatusValue]
) => {
  switch (statusVal) {
    case UserStatusValue.non:
      return "non";
    case UserStatusValue.init:
      return "init";
    case UserStatusValue.active:
      return "active";
  }
};
const key = getKeyUserStatus(2);
console.log(key); // => active

getKeyByValue()を作成することで、各定数オブジェクトに対してその都度、こんなswitch関数を作成していく必要がなくなる。

この関数の微妙なところ

const assertion されてないオブジェクトの場合、存在しない value を第二引数に指定できてしまう

as constされてないオブジェクトの場合、存在しないvalueも第二引数に指定可能になってしまう。その場合undefinedが返ってしまう。

export const UserStatusValue = {
  non: 0,
  init: 1,
  active: 2,
};

const key = getKeyByValue(UserStatusValue, 3); // エラーにならない
console.log(key); // => undefined

なので、as constを使った定数を前提とすることの認識を統一する必要などがありそう。

Generics が読みづらい

単純に読みづらい、かつ、ここまでGenericsを頑張って使う必要があるかも微妙な感じがする。

最後に

試行錯誤しながら、自分なりに書きました。
冒頭にも述べたように、もっと良いやり方、懸念点、指摘事項などあればコメントいただけると大変嬉しいです。

ありがとうございました。