How do I persist a ES6 Map in localstorage (or elsewhere)?

Assuming that both your keys and your values are serialisable,

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

should work. For the reverse, use

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

Clean as a whistle:

JSON.stringify([...myMap])

Usually, serialization is only useful if this property holds

deserialize(serialize(data)).get(key) ≈ data.get(key)

where a ≈ b could be defined as serialize(a) === serialize(b).

This is satisfied when serializing an object to 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 :)

And this works because properties can only be strings, which can be losslessly serialized into strings.

However, ES6 maps allow arbitrary values as keys. This is problematic because, objects are uniquely identified by their reference, not their data. And when serializing objects, you lose the references.

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 :(

So I would say in general it's not possible to do it in an useful way.

And for those cases where it would work, most probably you can use a plain object instead of a map. This will also have these advantages:

  • It will be able to be stringified to JSON without losing key information.
  • It will work on older browsers.
  • It might be faster.

Building off of Oriol's answer, we can do a little better. We can still use object references for keys as long as the there is primitive root or entrance into the map, and each object key can be transitively found from that root key.

Modifying Oriol's example to use Douglas Crockford's JSON.decycle and JSON.retrocycle we can create a map that handles this case:

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 :)

Decycle and retrocycle make it possible to encode cyclical structures and dags in JSON. This is useful if we want to build relations between objects without creating additional properties on those objects themselves, or want to interchangeably relate primitives to objects and visa-versa, by using an ES6 Map.

The one pitfall is that we cannot use the original key object for the new map (map3.get(key); would return undefined). However, holding the original key reference, but a newly parsed JSON map seems like a very unlikely case to ever have.