Try T.M Engineer Blog

多摩市で生息するエンジニアが「アウトプットする事は大事だ」と思って始めたブログ

Objectのデータを取得する時、ブラケット記法で書くと型がanyになって辛かった話

TypeScriptでObjectからデータを取得する時、ブラケット記法で書くと強制的に型がanyになって、大変辛い思いをしたので、以下に残しておく。

Objectのサンプルは以下の通り。ポイントは、exampleAB.a以降とexampleAB.b以降とで型が違うこと。

interface ExampleAB {
  'a': {
    a_example: {
      example: string
    }
  },
  'b': {
    b_example: {
      example: number
    }
  }
}

const exampleAB: ExampleAB = {
  'a': {
    a_example: {
      example: 'dummy'
    }
  },
  'b': {
    b_example: {
      example: 1
    }
  }
}

たとえば、以下のようにするとObjectからデータを取得することができる。

当たり前ですね。

console.log(exampleAB.a.a_example.example) // 'dummy'

しかし、これをブラケット記法で書くとTypeScript側で型を判断できずにエラーになる。

( T 0 T ) ォォォォ...

const k = 'a'
console.log(exampleAB[k as strng].a_example.example) // 型 'any' の式を使用して型 'ExampleAB' にインデックスを付けることはできないため、要素は暗黙的に 'any' 型になります。

keyofを使えばワンチャンあるかも・・・と思い、試すもダメだった。

const k = 'a'
console.log(exampleAB[k as keyof ExampleAB].a_example.example) // プロパティ 'a_example' は型 '{ a_example: { example: string; }; } | { b_example: { example: number; }; }' に存在しません。
  プロパティ 'a_example' は型 '{ b_example: { example: number; }; }' に存在しません。

どうやら、Objectの構造が深いとダメな模様。

Objectのデータ構造が、'exampleAB.a'と'exampleAB.b'までであれば、TypeScriptが判断してくれることを確認した。

interface ExampleAB2 {
  'a': string
  'b': number
}

const exampleAB2: ExampleAB2 = {
  'a': 'dummy1',
  'b': 1
}
const i = 'a'
console.log(exampleAB2[i as keyof ExampleAB2]) // 'dummy'

もうすこし、根気よくやってみよう。

「そうだ!interfaceを分けてみよう!」と思い、試してみた結果がこちら。。。

interface ExampleA {
  'a': {
    a_example: {
      example: string
    }
  }
}

interface ExampleB {
  'b': {
    b_example: {
      example: number
    }
  }
}

const exampleAB: (ExampleA | ExampleB) = {
  'a': {
    a_example: {
      example: 'dummy'
    }
  },
  'b': {
    b_example: {
      example: 1
    }
  }
}

const k = 'a'
const i = 'b'
console.log(exampleAB[k as keyof ExampleA].a_example.example) // 型 '"a"' の式を使用して型 'ExampleA | ExampleB' にインデックスを付けることはできないため、要素は暗黙的に 'any' 型になります。
  プロパティ 'a' は型 'ExampleA | ExampleB' に存在しません。
console.log(exampleAB[i as keyof ExampleB].b_example.example) // 型 '"b"' の式を使用して型 'ExampleA | ExampleB' にインデックスを付けることはできないため、要素は暗黙的に 'any' 型になります。
  プロパティ 'b' は型 'ExampleA | ExampleB' に存在しません。

orz... ダメだ...

ならば・・・と、exampleABに対して型アサーションを定義してあげればうまくいきました。

const k = 'a'
const i = 'b'
console.log((exampleAB as ExampleA)[k as keyof ExampleA].a_example.example) // 'dummy'
console.log((exampleAB as ExampleB)[i as keyof ExampleB].b_example.example) // 1

まさかブラケット記法で書くと型が強制的にanyになってしまうとは。。。。

TypeScriptで型を定義するのは、なかなか慣れないものですね。

最近は、QiitaでTypeScriptの型定義の演習問題を作ってくれている方がいて、こちらを使って修行しています。

qiita.com

とはいえ、なかなか問題が難しくて軽々解けない。

これが軽々解けるようになると、大幅にレベルアップできるかもしれない。。。そんな事を思いながら日々修行を続けていますm( = = )m