JavaScriptでObject.entries/fromEntriesでreduceの利用頻度を減らす

4 min read読了の目安(約3600字 2

entriesとは

JavaScriptにはObject.entries() / Array.prototype.entries() など、イテレーションができるものにはentitiesというものが実装されている。

これは要素を[key, value] の配列として変換するものだ。

また、Object.fromEntriesというものがある。これは逆に[key,value]で構成された配列をObjectへ変換するものになる。

Array / Map / Setはprototype実装されているので、someArray.entries()などできるが、Objectはそういうものを持てないので、Object.fromEntries(obj)とする必要がある

詳しくはMDNを参照してほしい

Object.fromEntriesを使ってreduceの利用回数を減らそう

例えば [{id: "foo", title: "hoge"}, {id: "baz", title: "fuga"}]{ foo: "hoge", baz: "fuga"}みたいにやりたいことは少なくない。そういう場合

const target =[{id: "foo", title: "hoge"}, {id: "baz", title: "fuga"}]

target.reduce((acc, cur) => {
  return {...acc, [cur.id]: cur.title}
} , {})

のようにreduceを利用することがよくある。ただreduce本来の目的ではないので、なかなかややこしくなってて可読性が辛くなる。

こういうときにObject.fromEntriesが使える

const target =[{id: "foo", title: "hoge"}, {id: "baz", title: "fuga"}]
Object.fromEntries(target.map((obj) => [obj.id, obj.title]))

MapをObjectに変換する

同じようなことをmapでやりたいとき、もう少し工夫が必要になる

const mapItem = new Map()
mapItem.set("foo", "hoge")
mapItem.set("baz", "fuga")
Object.fromEntries(mapItem.entries())

Map型はややこしいことに.map関数を持っていないので、代わりに.entries()を利用することになる

追記
コメントより。
この例はObject.fromEntries(mapItem)でOKだった。

const mapItem = new Map()
mapItem.set("foo", "hoge")
mapItem.set("baz", "fuga")
Object.fromEntries(mapItem)

ちなみにMapをmapしたいときは

mapItem.entries()ではIteratorが返ってくるので、mapItem.entries().mapも出来ない。

そこでArray.from()で配列に変換する

const mapItem = new Map()
mapItem.set("baz", {title: "hoge"})
mapItem.set("foo", {title: "hoge"})

Object.fromEntries(
  [...mapItem.entries()].map(( [key,value]) => [key,value.title])
  // spreadをしている部分は、下記と同義
  // Array.from(mapItem).map
)

entriesでMapに変換する

ObjectをMapに変換したいときもentriesは使える。

const obj = { foo: "hoge", baz: "fuga"} 

const mapItem = new Map(Object.entries(obj))
// const mapItem = new Map(obj) はエラーになる

配列をuniqueにしたいとき

よくあるreduceの利用として行われがちだった重複を取り除きたいときにも使えるだろう。

const arr = ["dog","cat","dog","dog","elephant"]

Object.keys(arr.reduce((acc, cur) => {
  return {...acc, [cur]: true}
}, {}))

これをfromEntriesを使うなら

const arr = ["dog","cat","dog","dog","elephant"]

Object.keys(Object.fromEntries(arr.map( (item) => [item, true])))

とはいえこの場合だとSet

const arr = ["dog","cat","dog","dog","elephant"]

const unique = [...new Set(arr)]

と記載できるので、こっちの方が良いだろう。

ObjectなのかMapなのかSetかわからないものを変換するなら

ObjectだったりMapだったりSetだったりが混ざったりするケース(あまりない)の場合はこんな具合になる。
(当然Object -> Objectの処理は割と無駄なことをやっている)

const objectOrMapToObject = (item) => {
  const itemEntries = typeof item.entries === "function"
    ? item.entries()
    : Object.entries(item)
  return Object.fromEntries(itemEntries)
}

Mapだけが混ざるとわかっているなら、item instanceof Mapなどの判定も使えるはずだ。[1]
例外的にentriesの関数を持つようなobjectがあるとコケる。もう少しちゃんとやりたければlodashのisMapなどを利用するのが良いだろう

脚注
  1. https://twitter.com/ymmooot/status/1377909770888257538 ↩︎