web-dev-qa-db-ja.com

ES6マップをlocalstorage(または他の場所)に永続化するにはどうすればよいですか?

_var a = new Map([[ 'a', 1 ]]);
a.get('a') // 1

var forStorageSomewhere = JSON.stringify(a);
// Store, in my case, in localStorage.

// Later:
var a = JSON.parse(forStorageSomewhere);
a.get('a') // TypeError: undefined is not a function
_

残念ながらJSON.stringify(a);は単に '{}'を返します。これは、復元時にaが空のオブジェクトになることを意味します。

私は es6-mapify を見つけました。これはマップとプレーンオブジェクトの間のアップ/ダウンキャストを可能にします。私の地図。

60
Letharion

キーと値の両方がシリアル化可能であると仮定すると、

localStorage.myMap = JSON.stringify(Array.from(map.entries()));

動作するはずです。逆の場合は、

map = new Map(JSON.parse(localStorage.myMap));
70
Bergi

通常、シリアル化は、このプロパティが保持される場合にのみ役立ちます

_deserialize(serialize(data)).get(key) ≈ data.get(key)
_

ここで、_a ≈ b_はserialize(a) === serialize(b)として定義できます。

これは、オブジェクトをJSONにシリアル化するときに満たされます。

_var obj1 = {foo: [1,2]},
    obj2 = JSON.parse(JSON.stringify(obj1));
obj1.foo; // [1,2]
obj2.foo; // [1,2] :)
JSON.stringify(obj1.foo) === JSON.stringify(obj2.foo); // true :)
_

また、プロパティは文字列のみであり、文字列にロスレスにシリアル化できるため、これは機能します。

ただし、ES6マップでは、任意の値をキーとして使用できます。オブジェクトはデータではなく参照によって一意に識別されるため、これは問題です。オブジェクトをシリアル化すると、参照が失われます。

_var key = {},
    map1 = new Map([ [1,2], [key,3] ]),
    map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()])));
map1.get(1); // 2
map2.get(1); // 2 :)
map1.get(key); // 3
map2.get(key); // undefined :(
_

だから私は言う一般的にそれは不可能です有用な方法でそれを行う。

そして、それが機能するケースでは、おそらくマップの代わりにプレーンなオブジェクトを使用するです。これには、次の利点もあります。

  • キー情報を失うことなく、JSONに文字列化できます。
  • 古いブラウザで動作します。
  • 速いかもしれません。
10
Oriol

Oriol'sanswer を基に、少し改善することができます。マップへのプリミティブルートまたは入り口があり、各オブジェクトキーがそのルートキーから推移的に検出できる限り、キーのオブジェクト参照を使用できます。

Oriolの例を修正してDouglas Crockfordの JSON.decycleとJSON.retrocycle を使用すると、このケースを処理するマップを作成できます。

_var key = {},
    map1 = new Map([ [1, key], [key, 3] ]),
    map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()]))),
    map3 = new Map(JSON.retrocycle(JSON.parse(JSON.stringify(JSON.decycle([...map1.entries()])))));
map1.get(1); // key
map2.get(1); // key
map3.get(1); // key
map1.get(map1.get(1)); // 3 :)
map2.get(map2.get(1)); // undefined :(
map3.get(map3.get(1)); // 3 :)
_

リサイクルとレトロサイクルを使用すると、JSONで循環構造とダグをエンコードできます。これは、オブジェクト自体に追加のプロパティを作成せずにオブジェクト間の関係を構築する場合、またはES6マップを使用して、プリミティブをオブジェクトとその逆に相互に関連付けたい場合に役立ちます。

落とし穴の1つは、新しいマップに元のキーオブジェクトをcannot使用しないことです(map3.get(key);は未定義を返します)。ただし、元のキー参照を保持しながら、新たに解析されたJSONマップは、これまでにない非常にまれなケースのようです。

5

Whiとしてきれいに:

JSON.stringify([...myMap])
5
Oded Breiner

多次元マップがある場合、受け入れられた答えは失敗します。 Mapオブジェクトは別のMapオブジェクトをキーまたは値として使用できることを常に覚えておく必要があります。

したがって、このジョブを処理するより適切で安全な方法は次のとおりです。

function arrayifyMap(m){
  return m.constructor === Map ? [...m].map(([v,k]) => [arrayifyMap(v),arrayifyMap(k)])
                               : m;
}

このツールがあれば、いつでも好きなことができます。

localStorage.myMap = JSON.stringify(arrayifyMap(myMap))
2
Redu

独自の toJSON() 関数をclassオブジェクトに実装すると、通常の古いJSON.stringify()動作するだけです!

Maps with Arrays with keys? Mapsと他のMapを値として?通常のMap内のObject?たぶん、あなた自身のカスタムクラスですら。簡単です。

_Map.prototype.toJSON = function() {
    return Array.from(this.entries());
};
_

それでおしまい! ここではプロトタイプの操作が必要です。 toJSON()をすべての非標準のものに手動で追加することもできますが、実際にはJSの力を避けているだけです。

[〜#〜] demo [〜#〜]

_test = {
    regular : 'object',
    map     : new Map([
        [['array', 'key'], 7],
        ['stringKey'     , new Map([
            ['innerMap'    , 'supported'],
            ['anotherValue', 8]
        ])]
    ])
};
console.log(JSON.stringify(test));
_

出力:

_{"regular":"object","map":[[["array","key"],7],["stringKey",[["innerMap","supported"],["anotherValue",8]]]]}
_

ただし、実際のMapsに逆シリアル化することは、自動ではありません。上記の結果の文字列を使用して、マップを作り直して値を引き出します。

_test2 = JSON.parse(JSON.stringify(test));
console.log((new Map((new Map(test2.map)).get('stringKey'))).get('innerMap'));
_

出力

_"supported"
_

それは少し面倒ですが、少し マジックソースで、デシリアライゼーションを自動マジックにすることもできます

_Map.prototype.toJSON = function() {
    return ['window.Map', Array.from(this.entries())];
};
Map.fromJSON = function(key, value) {
    return (value instanceof Array && value[0] == 'window.Map') ?
        new Map(value[1]) :
        value
    ;
};
_

JSONは

_{"regular":"object","test":["window.Map",[[["array","key"],7],["stringKey",["window.Map",[["innerMap","supported"],["anotherValue",8]]]]]]}
_

_Map.fromJSON_を使用すると、デシリアライズと使用は非常に簡単です

_test2 = JSON.parse(JSON.stringify(test), Map.fromJSON);
console.log(test2.map.get('stringKey').get('innerMap'));
_

出力(およびnew Map() sは使用されません)

_"supported"
_

[〜#〜] demo [〜#〜]

1
Hashbrown
// store
const mapObj = new Map([['a', 1]]);
localStorage.a = JSON.stringify(mapObj, replacer);

// retrieve
const newMapObj = JSON.parse(localStorage.a, reviver);

// required replacer and reviver functions
function replacer(key, value) {
  const originalObject = this[key];
  if(originalObject instanceof Map) {
    return {
      dataType: 'Map',
      value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject]
    };
  } else {
    return value;
  }
}
function reviver(key, value) {
  if(typeof value === 'object' && value !== null) {
    if (value.dataType === 'Map') {
      return new Map(value.value);
    }
  }
  return value;
}

私はここで代替機能とリバイバー機能に関する説明をここに書きました https://stackoverflow.com/a/56150320/696535

このコードは、通常のJSON.stringifyなどの他の値に対して機能するため、シリアル化されたオブジェクトがマップであるという前提はありません。また、配列またはオブジェクトに深くネストされたマップにすることもできます。

0
Pawel

除外されていることの1つは、Mapが[〜#〜] ordered [〜#〜]構造であるということです。つまり、最初に入力された項目を反復する場合最初にリストされます。

これは、Javascriptオブジェクトのような[〜#〜] not [〜#〜]です。このタイプの構造が必要だったので(私はMapを使用しました)、JSON.stringifyが機能しないことを見つけるのは苦痛です(しかし理解できます)。

最終的に「value_to_json」関数を作成しました。これは、すべてを解析することを意味します-最も基本的な「タイプ」に対してのみJSON.stringifyを使用します。

残念ながら、.toJSON()を使用したMAPのサブクラス化は、JSON_stringではなく値を除いて機能しません。また、レガシーと見なされます。

私のユースケースは例外的です。

関連:

function value_to_json(value) {
  if (value === null) {
    return 'null';
  }
  if (value === undefined) {
    return 'null';
  }
  //DEAL WITH +/- INF at your leisure - null instead..

  const type = typeof value;
  //handle as much as possible taht have no side effects. function could
  //return some MAP / SET -> TODO, but not likely
  if (['string', 'boolean', 'number', 'function'].includes(type)) {
    return JSON.stringify(value)
  } else if (Object.prototype.toString.call(value) === '[object Object]') {
    let parts = [];
    for (let key in value) {
      if (Object.prototype.hasOwnProperty.call(value, key)) {
        parts.Push(JSON.stringify(key) + ': ' + value_to_json(value[key]));
      }
    }
    return '{' + parts.join(',') + '}';
  }
  else if (value instanceof Map) {
    let parts_in_order = [];
    value.forEach((entry, key) => {
      if (typeof key === 'string') {
        parts_in_order.Push(JSON.stringify(key) + ':' + value_to_json(entry));
      } else {
        console.log('Non String KEYS in MAP not directly supported');
      }
      //FOR OTHER KEY TYPES ADD CUSTOM... 'Key' encoding...
    });
    return '{' + parts_in_order.join(',') + '}';
  } else if (typeof value[Symbol.iterator] !== "undefined") {
    //Other iterables like SET (also in ORDER)
    let parts = [];
    for (let entry of value) {
      parts.Push(value_to_json(entry))
    }
    return '[' + parts.join(',') + ']';
  } else {
    return JSON.stringify(value)
  }
}


let m = new Map();
m.set('first', 'first_value');
m.set('second', 'second_value');
let m2 = new Map();
m2.set('nested', 'nested_value');
m.set('sub_map', m2);
let map_in_array = new Map();
map_in_array.set('key', 'value');
let set1 = new Set(["1", 2, 3.0, 4]);

m2.set('array_here', [map_in_array, "Hello", true, 0.1, null, undefined, Number.POSITIVE_INFINITY, {
  "a": 4
}]);
m2.set('a set: ', set1);
const test = {
  "hello": "ok",
  "map": m
};

console.log(value_to_json(test));
0
hobbit_be