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