質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

18745閲覧

MapをJson文字列にする方法について

Lhankor_Mhy

総合スコア35865

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2017/02/16 01:16

編集2017/02/16 05:27

###前提・実現したいこと
https://teratail.com/questions/65726
上記質問に回答しまして、「連想配列をループするにはfor...inか、あるいはMapを用いるとよい」とコメントしました。

http://qiita.com/raccy/items/bf590d3c10c3f1a2846b#no_entry_sign-delete
を読むと、連想配列にはMapを用いた方がよい、という流れのようです。

であるのですが、よく考えるとMapはJSON.stringifyでJSON文字列に変換するのにひと工夫必要であるかと思うのですが、よい方法はありますか?
それとも、上記質問のようなJSON.stringifyを利用することが想定される場合はMapを用いない方がいいのでしょうか。

###発生している問題・エラーメッセージ
特になし

###該当のソースコード

javascript

1JSON.stringify( Array.from(map).reduce( (sum, [v,k]) => (sum[v]=k, sum), {} ) );

###試したこと
ぱっと思いついたコードは上記のような感じなんですが、ちょっと意図が分かりにくい感じですし、ディープにやろうとするとそこそこ面倒そうです。
中級者の方々なら大丈夫なんでしょうけれど、ちょっと初心者にはハードルが高いような?

###自分の考え
tomohiro_obaraさんより「自分の考えを書くように」という旨の追記依頼をいただきましたので別項を設けて記述します。
私の意見としては、JSON.stringifyがMapに対応していないこと、特に階層の深いMapの場合、JSONのシリアライズ・パースが手間であることから、初心者であっても、MapよりObjectを利用し、走査する場合はObject.keysとfor...ofを用いる方が、現実的なのではないかと感じました。

それはそれとして、MapをJSON文字列に変換するよい方法があれば教えていただきたいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

turbgraphics200

2017/02/16 02:03

Mapオブジェクトとmap()メソッドを混同してません?
Lhankor_Mhy

2017/02/16 02:11

してないと思います。どこをご覧になってそのように感じられましたか?
t_obara

2017/02/16 02:14

参照されている記事に記載されていますが、「一度自分で理由を考えてください」をした上で、どのようにお考えになったのか、ご提示されるとよろしいのではないでしょうか。「ディープにやろうとすると」の意味がはかりかねました。
turbgraphics200

2017/02/16 02:15

いや、リンク先が#for...inになってたので。リンク張るなら#deleteではないか。というところです。
Lhankor_Mhy

2017/02/16 02:24

turbgraphics200さん、ご指摘ごもっともです。修正しておきます。
Lhankor_Mhy

2017/02/16 02:28

tomohiro_obaraさん、私なりの考えは質問文に書いたつもりでした。分かりにくかったようなので別項を設けます。
t_obara

2017/02/16 05:41

参照先の指摘は、「連想配列にはMapが良い」に対してです。今回のようなケースで、本当にMapが良いのですか?参照先ではMapが良いとされていることに対して、鵜呑みにして、Mapにしたけれど、JSONにしにくいということが課題となっている。この状況について、本当によく考えていて、その結果、やはりMapからjsonに変換する方法が課題として残っているということなのですか?
Lhankor_Mhy

2017/02/16 06:28 編集

分かりにくくて申し訳ないのですが、私の「Mapを採用すべきかObjectを採用すべきか」という問題に対しての評価は、質問文に『MapよりObjectを利用し、走査する場合はObject.keysとfor...ofを用いる方が、現実的なのではないかと感じました』と記述した通りです。
guest

回答3

0

ベストアンサー

Map の key と value のペアを配列として持ち、復元時にはそれを使って Map を再現するという下記の手法はどうでしょう。

【Converting ES6 Maps to and from JSON】
http://www.2ality.com/2015/08/es6-map-json.html

投稿2017/02/16 02:58

kei344

総合スコア69364

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Lhankor_Mhy

2017/02/16 04:20

ああ、なるほどです。スプレッド演算子で展開すればいいんですね。 しかし、これでもディープなマップを変換する時には探索をしないといけないですよね……
kei344

2017/02/16 05:21

当然そうなりますね。JSONの仕様が変わらない以上そういった変換関数をつくるしかないかなと思います。
Lhankor_Mhy

2017/02/16 08:33

JSONが変換するMapの構造を表現してるとは言えないので、シリアライズではあるけどJSON変換と言えるかどうか微妙ですね…… たとえばサーバとやりとりするためには、パースするだけではダメで、たとえばPythonでいうとdict関数を通す必要がありますね。 上記質問のようなlocalStorageへのシリアライズ目的とかにはお手軽で便利ですね。
kei344

2017/02/16 14:52

> 上記質問のようなlocalStorageへのシリアライズ目的 これについては単純な配列のほうが良いと思います。オブジェクトを配列で持ってそれを利用させるのが良いと思います。
guest

0

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 さん

投稿2017/02/16 14:49

編集2017/02/16 14:51
think49

総合スコア18156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Lhankor_Mhy

2017/02/17 00:55

おお、ほんとですね。replacerを使って再帰させるようなコードをかければかなりスマートですね。ちょっと取り組んでみます。 ところで、あのガイドラインについてですが、「分からないならここに書いてあることに従った方がいい」というガイドラインなのですから、「連想配列目的ではMapを使う」とある場合、『「JSON」「オブジェクト初期化子」「Map」の全ての仕様の違いを認識して』いない者は連想配列目的ではMapを使うべきである、と読むしかないと思いますので、ちょっとした罠ではあるよなあ、とは思いました。 まあ、著者に言えって話ですよね。
guest

0

javascript

1Map.prototype.toJSON = function(){ 2 return Array.from(this).reduce( (sum, [v,k]) => (sum[v]=k, sum), {} ) 3}; 4 5var map = new Map([['a',1],['b',2]]); 6map.set('a',new Map([['c',3]])); 7JSON.stringify(map); 8 9/* 10{"a":{"c":3},"b":2} 11*/

プロトタイプ汚染をして階層のディープなMap構造をJSONにする方法を思いついたのですが、パースが手間ですねえ……

追記

javascript

1(o => { 2 return (function f(o){ 3 if (Object.getPrototypeOf(o) === Object.prototype){ 4 return new Map( Object.keys(o).map(k=> [k, f(o[k])]) ); 5 } else { 6 return o; 7 } 8 })(o) 9})(JSON.parse(str));

こんな感じか、うーん……

ひらめいた

これならプロトタイプ汚染されないんじゃないかな?

javascript

1let str='{"a":1}'; 2let obj = Object.assign(Object.create(null), JSON.parse(str));

投稿2017/02/17 11:35

編集2017/02/25 11:08
Lhankor_Mhy

総合スコア35865

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問