Mapオブジェクトと連想配列を変換したくなったのですが、一発で変換できないみたいなので自分で書いてみました。
調べながら書いていろいろ勉強になったのでメモしておきます。
できたもの
const mapToObject = map =>
Array.from(map.entries())
.map(([k,v]) => ({[k]:v}))
.reduce((l,r) => Object.assign(l, r), {})
const objectToMap = object =>
Object.entries(object).reduce((l,[k,v])=>l.set(k,v), new Map())
追記
編集リクエストやコメントをもらって最終的にできたもの
const mapToObject = map =>
[...map].reduce((l,[k,v]) => Object.assign(l, {[k]:v}), {})
const objectToMap = object =>
new Map(Object.entries(object))
説明
Mapを連想配列に変換
const map = new Map()
map.set('id', 1)
map.set('name', 'me')
// map -> Map {id: 1, name: 'me'}
let tmp = map.entries(map)
// tmp -> MapIterator{ ['id', 1], ['name', 'me'] }
tmp = Array.from(tmp)
// tmp -> [ ['id', 1], ['name', 'me'] ]
tmp = tmp.map(([k,v]) => ({[k]:v}))
// tmp -> [ {id: 1}, {name: 'me'} ]
const result = tmp.reduce((l,r) => Object.assign(l, r), {})
// result -> {id: 1, name:'me'}
Mapからイテレータを取り出す
はじめにMap#entriesメソッドで、mapから「キーと値のペア」のイテレータを取り出します。
let tmp = map.entries(map)
// tmp -> MapIterator{ ['id', 1], ['name', 'me'] }
イテレータを配列に変換
イテレータというのは配列ではないので、map
もreduce
も使えません。
不便なので配列に変換します。Array.fromメソッドにイテレータを渡すと配列に変換してくれます。これでやっと配列オブジェクトの便利なメソッドが使えるようになりました。
tmp = Array.from(a)
// tmp -> [ ['id', 1], ['name', 'me'] ]
配列を連想配列に変換
配列の要素である各「キーと値のペア」は、それぞれが長さ2の配列になっていて、0番目にキー、1番目に値が入っています。これを連想配列にします。
分割代入
ES6の分割代入という機能で、配列引数を最初から展開された状態で受け取れるようになりました。
const plus = nums => nums[0] + nums[1]
// ↓
const plus = ([a, b]) => a + b
Computed property names
さらに、連想配列のキー名に変数を使った書き方ができるようになりました。
オブジェクト初期化子 - Computed property names
const key1 = 'id'
const key2 = 'name'
const obj = {[key1]: 1, [key2]: 'me'} // -> {id: 1, name: 'me'}
これらを使用して、長さ2の配列を連想配列に変換します。外側の配列の要素全てに対してArray#mapで行います。
tmp.map(([k,v]) => ({[k]:v}))
// tmp -> [ {id: 1}, {name: 'me'} ]
結合して完成
連想配列の配列になったので、全ての連想配列をObject.assignで結合したらできあがりです。
配列の要素を全部結合するのにArray#reduceを使っています。
tmp.reduce((l,r) => Object.assign(l, r), {})
// tmp -> {id: 1, name:'me'}
連想配列をMapに変換
こっちは簡単でした。
const object = {id: 1, name: 'me' }
// object -> {id: 1, name: 'me' }
let tmp = Object.entries(object)
// tmp -> [ ['id', 1], ['name', 'me'] ]
const result = new Map(tmp)
// result -> Map {id: 1, name: 'me' }
Object.entriesはES2017の機能ですが、IE11以外のブラウザならだいたい動きます。nodejsでは8.7以降くらいから動くようです。
編集リクエストをいただきました!
※ 1月18日更新
Mapから連想配列に変換する処理について、編集リクエストをもらいました!
@hoo-chan さんありがとうございますm(_ _)m
プルリクをもらったみたいでうれしいですね(´∀`*)
const mapToObject = map =>
[...map].reduce((l,[k,v]) => Object.assign(l, {[k]:v}), {})
Mapオブジェクトは最初からイテレータブル
Mapオブジェクトは、それ自体がイテレータブルなので、Array.from
にそのまま渡すことができます。
さらに、イテレータブルってことはスプレッド演算子で配列に展開できそうです。
Array.from(map.entries())
// map -> [ [ 'id', 1 ], [ 'name', 'me' ] ]
👇
Array.from(map)
// map -> [ [ 'id', 1 ], [ 'name', 'me' ] ]
👇
[...map]
// map -> [ [ 'id', 1 ], [ 'name', 'me' ] ]
map().reduce()は冗長
Array#map
で変換してからArray#reduce
に渡してましたが、reduceの中で変換もすれば一つにまとめられます。
tmp.map(([k,v]) => ({[k]:v}))
.reduce((l,r) => Object.assign(l, r), {})
👇
tmp.reduce((l,[k:v]) => Object.assign(l, {[k]:v}), {})
完成
最終的にこうなりました。
スッキリしましたね。
const mapToObject = map =>
Array.from(map.entries())
.map(([k,v]) => ({[k]:v}))
.reduce((l,r) => Object.assign(l, r), {})
↓
const mapToObject = map =>
[...map].reduce((l,[k,v]) => Object.assign(l, {[k]:v}), {})
ループで回した方がわかりやすい
Mapオブジェクトがイテレータブルってことはfor ... of
のループで回すことができます。関数型っぽい書き方にこだわらなければこっちの方がわかりやすくてよいですね。
const mapToArray = map => {
const result = {}
for (const [key, value] of map) result[key] = value
return result
}