LoginSignup
77
72

[JavaScript] Mapと連想配列の相互変換

Last updated at Posted at 2018-01-17

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'] }

イテレータを配列に変換

イテレータというのは配列ではないので、mapreduceも使えません。
不便なので配列に変換します。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
}
77
72
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
77
72