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

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

ただいまの
回答率

90.54%

  • JavaScript

    19935questions

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

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 5,443

Lhankor_Mhy

score 8796

前提・実現したいこと

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を用いない方がいいのでしょうか。

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

特になし

該当のソースコード

JSON.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文字列に変換するよい方法があれば教えていただきたいです。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • Lhankor_Mhy

    2017/02/16 11:28

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

    キャンセル

  • t_obara

    2017/02/16 14:41

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

    キャンセル

  • Lhankor_Mhy

    2017/02/16 15:26 編集

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

    キャンセル

回答 3

checkベストアンサー

+3

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/16 13:20

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

    キャンセル

  • 2017/02/16 14:21

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

    キャンセル

  • 2017/02/16 17:33

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

    上記質問のようなlocalStorageへのシリアライズ目的とかにはお手軽で便利ですね。

    キャンセル

  • 2017/02/16 23:52

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

    キャンセル

+2

 Map はオブジェクト初期化子の上位互換

まず、大原則として Map を配列やオブジェクト初期化子で表現することは出来ません。
Map はオブジェクト初期化子の上位互換であり、JSON もオブジェクト初期化子を完全再現する事は出来ないので、JSON に変換する時に抜け落ちる情報がどうしても出来てしまいます。
初心者が超えるべき壁は両者の性質の違いを理解する事ですが、Map だけを使用してきた人には覚える事が出来ません。

  • オブジェクト初期化子は String 型 / Symbol 型のキーしか持つことが出来ない
  • JSON は String 型のキーしか持つことが出来ない
  • オブジェクト初期化子はキーの列挙順を定義できない

従って、これらの挙動に頼らないように Map オブジェクト内のデータに制約を付ける事で互換性を確保します。

 一次元 Map を JSON に変換する

一次元構造なら既出の通り、スプレッド演算子で Map を二次元配列に変換できます。

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 が多次元構造になると話は別です。

'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オブジェクトがあったとしたらどうでしょう?

console.log(mapToJson([['foo', new Map([['bar', 1]])]])); // [["foo",[["bar",1]]]]
console.log(mapToJson([['foo', [['bar', 1]]]]));          // [["foo",[["bar",1]]]]

両者は別々の構造をしていますが、JSON化すると同じオブジェクトに見えてしまいます。
「内部でオブジェクト初期化子を使わない」という制約を設けるなら次のように書いて対応できます。

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/17 08:46

    今、コードを書ける環境にないので後になって思いついたアイデアを追記。
    Mapオブジェクトと同様の記法 {"object-initializer":{a:1}}} にすれば、オブジェクト初期化子もJSON化する事が可能ですね。
    また、JSON.stringify() の第二引数を利用すれば、洗練されたコードが書けそうです。
    https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

    キャンセル

  • 2017/02/17 09:55

    おお、ほんとですね。replacerを使って再帰させるようなコードをかければかなりスマートですね。ちょっと取り組んでみます。

    ところで、あのガイドラインについてですが、「分からないならここに書いてあることに従った方がいい」というガイドラインなのですから、「連想配列目的ではMapを使う」とある場合、『「JSON」「オブジェクト初期化子」「Map」の全ての仕様の違いを認識して』いない者は連想配列目的ではMapを使うべきである、と読むしかないと思いますので、ちょっとした罠ではあるよなあ、とは思いました。

    まあ、著者に言えって話ですよね。

    キャンセル

0

Map.prototype.toJSON = function(){
  return Array.from(this).reduce( (sum, [v,k]) => (sum[v]=k, sum), {} )
};

var map = new Map([['a',1],['b',2]]);
map.set('a',new Map([['c',3]]));
JSON.stringify(map);

/*
{"a":{"c":3},"b":2}
*/


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

 追記
(o => {
  return (function f(o){
    if (Object.getPrototypeOf(o) === Object.prototype){
      return new Map( Object.keys(o).map(k=> [k, f(o[k])]) );
    } else {
      return o;
    }
  })(o)
})(JSON.parse(str));


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

 ひらめいた

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

同じタグがついた質問を見る

  • JavaScript

    19935questions

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