🫱

TypeScriptで、オブジェクトに対して動的にキーアクセスしたい

2024/04/11に公開

はじめに

JavaScript→TypeScriptへ移行する際に、オブジェクトに対するアクセスで型チェックエラーが出て困っていました。

const obj = {
    key1: "hoge",
    key2: "fuga",
    key3 : ""
}
// →
// const obj: {
//     key1: string;
//     key2: string;
//     key3: string;
// }
// が型推論される

const objArray: string[] = [];

// 型チェックエラーが発生する
for (let i = 1; i <= 3; i++) {
    if (obj[`key${i}`]) {
        objArray.push(obj[`key${i}`]);
    }
}
console.log(objArray);

objにはkey1, key2, key3のキーしか存在しないため、obj[`key${i}`]という形でアクセスしようとすると、コンパイルエラーになります。

上記の場合では for文中でiは1, 2, 3のいずれかに限られますが、TypeScriptではそこまで反映しての型チェックはできないです。
今回に限らず、TypeScriptの型チェックは必ずしも完璧ではないことに留意しましょう。
今回も、存在しないプロパティにアクセスする可能性があるためにエラーが発生しています。

対策1

type Obj = {
    [key: `key${number}`]: string;
}
const obj: Obj = {
    key1: "hoge",
    key2: "fuga",
    key3 : ""
}

const objArray: string[] = [];
for (let i = 0; i <= 3; i++) {
    if (obj[`key${i}`]) {
        objArray.push(obj[`key${i}`]);
    }
}
console.log(objArray);

[key: `key${number}`]: string;といったインデックス型を用いて、型定義してあげればいいのではないかと思い、上記コードを書きました。

型チェックは問題なく突破できるものの、

  • 型定義をしなければいけないのでコード量が増えてしまう
  • 実際には存在しないキー(key4など)へのアクセスも型定義上できてしまう
// TypeScriptでの型チェックは通るがランタイムエラーが発生する
console.log(obj.key4);

といったデメリットがあり、ベストな回答とは言えない気がします。

対策2

悩んだ結果、下記コードに辿り着きました。
Object.keys()やObject.values()を用いることで、オブジェクトに対する安全な動的アクセスが可能です。
型チェックに引っ掛かることも、型安全性が破壊されランタイムエラーが発生することもないです。

const obj = {
    key1: "hoge",
    key2: "fuga",
    key3 : ""
}

const objArray = Object.values(obj).filter(value => value);
console.log(objArray);

参考文献

https://typescriptbook.jp/reference/values-types-variables/object/index-signature

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/values

Discussion