オブジェクトのプロパティをstirigでなくユニオン型で取得したい

TSで特定のオブジェクトのプロパティをObject.keysで取得したいケースでちょっとハマったのでメモ書きとして残します。

やりたかったこと

目的はシンプルで、以下のようなHoge型があったとします。

const Hoge = {
  ["aaaa"]: "A",
  ["bbbb"]: "B",
} as const;

このHogeのキーをループで回した場合、通常は以下のように書くと思います。

Object.keys(Hoge).forEach((key) => {
    console.log(Hoge[key]); // --> "A"とか"B"とかを表示
});

しかし、これだとエラーが出ます。 内容は下記の通りです。

TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly aaaa: "A"; readonly bbbb: "B"; }'.
  No index signature with a parameter of type 'string' was found on type '{ readonly aaaa: "A"; readonly bbbb: "B"; }'.

どうやらObject.keys()string[]を返すらしく、Hogeのキーに合致しない可能性があるため当該のエラーが出ているようです。

どうにかしてObject.keys(Hoge)のレスポンスはstring[]ではなくHogeのキー文字列のユニオン型の配列だと表現したいです。

keyof typeofを使う

Hogekey(あるいはvalue)のユニオン型は以下のようにして作ることができます。

type HogeKeys = keyof typeof Hoge; // --> "aaaa" | "bbbb"
type HogeValues = typeof Hoge[HogeKeys]; // --> "A" | "B"

これを利用することで、先ほどのObject.keysのレスポンスをstring[]ではなくします。

// Object.keys(Hoge)をHogeKeys[]型とする
(Object.keys(Hoge) as (HogeKeys)[]).forEach((key) => {
    console.log(Hoge[key]);
});

これでエラーが出なくなりました。

まとめ

Typescriptは型付けができて非常に便利ですが、今回のようなケースやEnumを絡めたようなケースだと型パズルにハマってしまうことがあります。 都度調べるしかないですが、今回の内容は色々と応用が効きそうなので備忘録として記事にしました。

今回の内容が役立ちましたら幸いです。

SNSでシェアする