回答編集履歴

1 typo修正

think49

think49 score 13462

2017/02/16 23:51  投稿

### Map はオブジェ気宇と初期化子の上位互換
### Map はオブジェクト初期化子の上位互換
まず、大原則として `Map` を配列やオブジェクト初期化子で表現することは出来ません。
`Map` はオブジェクト初期化子の上位互換であり、`JSON` もオブジェクト初期化子を完全再現する事は出来ないので、JSON に変換する時に抜け落ちる情報がどうしても出来てしまいます。
初心者が超えるべき壁は両者の性質の違いを理解する事ですが、`Map` だけを使用してきた人には覚える事が出来ません。
- オブジェクト初期化子は String 型 / Symbol 型のキーしか持つことが出来ない
- JSON は String 型のキーしか持つことが出来ない
- オブジェクト初期化子はキーの列挙順を定義できない
従って、これらの挙動に頼らないように `Map` オブジェクト内のデータに制約を付ける事で互換性を確保します。
### 一次元 Map を JSON に変換する
一次元構造なら既出の通り、スプレッド演算子で `Map` を二次元配列に変換できます。
```JavaScript
var map = new Map([['foo', 1], ['bar', 2], [10, 'piyo']]),
   array = [...map];
console.log(JSON.stringify(array)); // [["foo",1],["bar",2],[10,"piyo"]]
```
### 多次元 Map を JSON に変換する
しかし、`Map` が多次元構造になると話は別です。
- [new Map を JSON に変換する - JSFiddle](https://jsfiddle.net/ahn6psf4/1/)
```JavaScript
'use strict';
const mapToArray = (map) => {
 if (Object.getPrototypeOf(map) === Map.prototype) {
   map = [...map];
   let i = map.length;
   while (i--) {
     const entry = map[i],
           value = entry[1];
     if (Object.getPrototypeOf(value) === Map.prototype || Array.isArray(value)) {
       entry[1] = mapToArray(value);
     }
   }
 } else if (Array.isArray(map)) {
   let i = map.length;
   while (i--) {
     const value = map[i];
     if (Object.getPrototypeOf(value) === Map.prototype || Array.isArray(value)) {
       map[i] = mapToArray(value);
     }
   }
 }
 return map;
}
const mapToJson = map => JSON.stringify(mapToArray(map));
console.log(mapToJson([['foo', 1], ['bar', 2], [10, 'piyo']])); // [["foo",1],["bar",2],[10,"piyo"]]
console.log(mapToJson(new Map([['foo', 1], ['bar', new Map([['key1', 'value1'], ['key2', 'value2']])], [10, [1,new Map([['hoge', 'fuga']]),3]]]))); // [["foo",1],["bar",[["key1","value1"],["key2","value2"]]],[10,[1,[["hoge","fuga"]],3]]]
```
このコードは一見、期待通りに動作しているように見えます。
しかし、次のMapオブジェ気宇とがあったとしたらどうでしょう?
しかし、次のMapオブジェクトがあったとしたらどうでしょう?
```JavaScript
console.log(mapToJson([['foo', new Map([['bar', 1]])]])); // [["foo",[["bar",1]]]]
console.log(mapToJson([['foo', [['bar', 1]]]]));         // [["foo",[["bar",1]]]]
```
両者は別々の構造をしていますが、JSON化すると同じオブジェクトに見えてしまいます。
「内部でオブジェクト初期化子を使わない」という制約を設けるなら次のように書いて対応できます。
- [new Map を JSON に変換する - JSFiddle](https://jsfiddle.net/ahn6psf4/2/)
```JavaScript
'use strict';
const mapToArray = (map) => {
 if (Object.getPrototypeOf(map) === Map.prototype) {
   map = [...map];
   let i = map.length;
   while (i--) {
     const entry = map[i],
           value = entry[1];
     if (Object.getPrototypeOf(value) === Map.prototype || Array.isArray(value)) {
       entry[1] = mapToArray(value);
     }
   }
   map = {'map-object': map};
 } else if (Array.isArray(map)) {
   let i = map.length;
   while (i--) {
     const value = map[i];
     if (Object.getPrototypeOf(value) === Map.prototype || Array.isArray(value)) {
       map[i] = mapToArray(value);
     }
   }
 }
 return map;
}
const mapToJson = map => JSON.stringify(mapToArray(map));
console.log(mapToJson([['foo', 1], ['bar', 2], [10, 'piyo']])); // [["foo",1],["bar",2],[10,"piyo"]]
console.log(mapToJson(new Map([['foo', 1], ['bar', new Map([['key1', 'value1'], ['key2', 'value2']])], [10, [1,new Map([['hoge', 'fuga']]),3]]]))); // {"map-object":[["foo",1],["bar",{"map-object":[["key1","value1"],["key2","value2"]]}],[10,[1,{"map-object":[["hoge","fuga"]]},3]]]}
console.log(mapToJson([['foo', new Map([['bar', 1]])]])); // [["foo",{"map-object":[["bar",1]]}]]
console.log(mapToJson([['foo', [['bar', 1]]]]));         // [["foo",[["bar",1]]]]
```
### 初心者は Map を使うべきなのか
繰り返しになりますが、JSON 化する為には「JSON」「オブジェクト初期化子」「Map」の全ての仕様の違いを認識しておく必要があります。
「Mapを使うべきか」以前にすべてを知っていなければJSON化する為のコードを書くのは無理だと私は思います。
件の規約は古い技術に頼って罠にはまるよりも最新の技術を使って安全な道をナビゲートする役目を期待しているのだと思いますが、このレベルになってくると技術を選り好みしている段階ではないと思います。
最終的にはどちらも習得する必要があり、学ぶ順序としては ES5 -> ES6 の順番が覚えやすい(ES6 は ES5 習得前提の解説が大多数)と思っているので、私としてはオブジェクト初期化子を覚えてから new Map を覚える方が初心者向きであると考えています。
Re: Lhankor_Mhy さん

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る