[JS, TS] オブジェクトの中にkey-valueのオブジェクトを格納するときの型

3 min読了の目安(約3200字TECH技術記事

TypeScript + Reactでアプリを作っています。
データはいったんDBではなく配列に定義しているのですが、
それをオブジェクトに変換する必要が出てきました。

その際型も変更する必要があるのですが、
型変換でつっかかったので共有しておきます。

前提

JavaScriptでcategoriesという配列の変数を定義したとします。

const categories = [
  {
    id: 1,
    title: 'Hats',
    routeName: 'hats'
  },
  {
    id: 2,
    title: 'Jackets',
    routeName: 'jackets'
  }
]

このcategoriesに型をつける場合、
下記のようなtypeを定義すれば問題ないですよね。

interface Category {
  id: number,
  title: string,
  routeName: string
}

const categories: Category[] = [
  {
    id: 1,
    title: 'Hats',
    routeName: 'hats'
  },
  {
    id: 2,
    title: 'Jackets',
    routeName: 'jackets'
  }
]

Categoryというオブジェクトが配列の中に複数存在するので、
categoriesはCategoryオブジェクトの配列、ということでCategory[]としています。

categoriesを配列からオブジェクトに変えると…?

じゃあcategoriesを配列からオブジェクトに変えます。
オブジェクトのkeyにはcategoryのrouteNameを使用します。

// []から{}に変更
const categories = {
  // key: valueに変更
  'hats': {
    id: 1,
    title: 'Hats',
    routeName: 'hats',
  },
  'jackets': {
    id: 2,
    title: 'Jackets',
    routeName: 'jackets',
  }
}

これに対して型はどのように変更すればよいでしょうか?
それを考えるために、まず配列の場合とオブジェクトの場合の違いをあげてみます。
※以下「Obj」は「オブジェクト」を指します

1. const categoriesの型が変わった  
   (配列) const categories = [...]  
   (Obj) const categories = {...}

2. const categories内部のオブジェクトの構造が変わった  
   (配列) { ... }, { ... }, ...  
   (Obj) 'hats': { ... }, 'jackets': { ... }, ...  

上記が主な変更点です。
この変更によって、

(配列) 「Categoryというオブジェクト」が「配列」の中に複数存在する
(Obj) 「{ key: Category }というオブジェクト」が「オブジェクト」の中に複数存在する

という状況に変わりました。

格納するオブジェクトが単体の場合

では、どのように型を変更するかも考えてみます。
categoriesをオブジェクトに変更することで、その内部のオブジェクトは

'hats': { ... }

という形に変わりました。
この場合、key: string, value: Categoryという関係になるので、この型は

type KeyValueCategory = [key: string]: Category

と書けそうです。


格納するオブジェクトが複数の場合

問題はこれが複数存在するときです。

(配列) KeyValueCategoryが配列の中に複数存在する
=> KeyValueCategory[]

(Obj) KeyValueCategoryがオブジェクトの中に複数存在する
=> ???

オブジェクトの場合について、KeyValueCategory{}なんて構文は存在しないだろうし、
複数の場合どうやって定義すればいいのだろう…

ここで少し考えてしまいました。

結論

オブジェクトの中に{ key: value }の形のオブジェクトが「複数」格納される場合、
key: valueを定義した型は単数・複数を考えなくていいようです↓


interface Category {
  id: number,
  title: string,
  routeName: string
}

type KeyValueCategory = { [key: string]: Category }

// 内部のオブジェクトが単数・複数かを考慮しなくてよい
const categories: KeyValueCategory = {
  'hats': {
    id: 1,
    title: 'Hats',
    routeName: 'hats'
  },
  'jackets': {
    id: 2,
    title: 'Jackets',
    routeName: 'jackets'
  }
}



これでビルドが通るようになりました。

まとめ

オブジェクトの中にkey: valueのオブジェクトを格納する場合、
そのオブジェクトの数が単数だろうが複数だろうが同じオブジェクト型をつければよい

比較用

【配列】

interface Category {
  id: number,
  title: string,
  routeName: string
}

const categories: Category[] = [
  {
    id: 1,
    title: 'Hats',
    routeName: 'hats'
  },
  {
    id: 2,
    title: 'Jackets',
    routeName: 'jackets'
  }
]

【オブジェクト】

interface Category {
  id: number,
  title: string,
  routeName: string
}

type KeyValueCategories = { [key: string]: Category }

const categories: KeyValueCategories = {
  'hats': {
    id: 1,
    title: 'Hats',
    routeName: 'hats'
  },
  'jackets': {
    id: 2,
    title: 'Jackets',
    routeName: 'jackets'
  }
}