How do you JSON.stringify an ES6 Map?

115,162

Solution 1

Both JSON.stringify and JSON.parse support a second argument. replacer and reviver respectively. With replacer and reviver below it's possible to add support for native Map object, including deeply nested values

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

Usage:

const originalValue = new Map([['a', 1]]);
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, newValue);

Deep nesting with combination of Arrays, Objects and Maps

const originalValue = [
  new Map([['a', {
    b: {
      c: new Map([['d', 'text']])
    }
  }]])
];
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, newValue);

Solution 2

You can't directly stringify the Map instance as it doesn't have any properties, but you can convert it to an array of tuples:

jsonText = JSON.stringify(Array.from(map.entries()));

For the reverse, use

map = new Map(JSON.parse(jsonText));

Solution 3

You can't.

The keys of a map can be anything, including objects. But JSON syntax only allows strings as keys. So it's impossible in a general case.

My keys are guaranteed to be strings and my values will always be lists

In this case, you can use a plain object. It will have these advantages:

  • It will be able to be stringified to JSON.
  • It will work on older browsers.
  • It might be faster.

Solution 4

While there is no method provided by ecmascript yet, this can still be done using JSON.stingify if you map the Map to a JavaScript primitive. Here is the sample Map we'll use.

const map = new Map();
map.set('foo', 'bar');
map.set('baz', 'quz');

Going to an JavaScript Object

You can convert to JavaScript Object literal with the following helper function.

const mapToObj = m => {
  return Array.from(m).reduce((obj, [key, value]) => {
    obj[key] = value;
    return obj;
  }, {});
};

JSON.stringify(mapToObj(map)); // '{"foo":"bar","baz":"quz"}'

Going to a JavaScript Array of Objects

The helper function for this one would be even more compact

const mapToAoO = m => {
  return Array.from(m).map( ([k,v]) => {return {[k]:v}} );
};

JSON.stringify(mapToAoO(map)); // '[{"foo":"bar"},{"baz":"quz"}]'

Going to Array of Arrays

This is even easier, you can just use

JSON.stringify( Array.from(map) ); // '[["foo","bar"],["baz","quz"]]'

Solution 5

Using spread sytax Map can be serialized in one line:

JSON.stringify([...new Map()]);

and deserialize it with:

let map = new Map(JSON.parse(map));
Share:
115,162

Related videos on Youtube

rynop
Author by

rynop

Twitter: @rynop

Updated on December 24, 2021

Comments

  • rynop
    rynop over 2 years

    I'd like to start using ES6 Map instead of JS objects but I'm being held back because I can't figure out how to JSON.stringify() a Map. My keys are guaranteed to be strings and my values will always be listed. Do I really have to write a wrapper method to serialize?

    • David Chase
      David Chase about 6 years
      interesting article on the topic 2ality.com/2015/08/es6-map-json.html
    • PatS
      PatS about 6 years
      I was able to get this to work. The results are on Plunkr at embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP. The solution uses a JSON.stringify(obj, replacerFunction) which checks to see if a Map object is being passed and converts the Map object to a Javascript object (that JSON.stringify) will then convert to a string.
    • user56reinstatemonica8
      user56reinstatemonica8 about 6 years
      If your keys are guaranteed to be strings (or numbers) and your values arrays, you can do something like [...someMap.entries()].join(';'); for something more complex you could try something similar using something like [...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
    • Franklin Yu
      Franklin Yu over 5 years
      @Oriol What if it is possible for key name to be same as default properties? obj[key] may get you something unexpected. Consider the case if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
  • Capaj
    Capaj over 8 years
    for the curious-in the latest chrome, any map serializes into '{}'
  • Oriol
    Oriol over 8 years
    I have explained here what exactly I meant when I said "you can't".
  • Lilleman
    Lilleman over 8 years
    "It might be faster" - Do you have any source on that? I'm imagining a simple hash-map must be faster than a full blown object, but I have no proof. :)
  • Oriol
    Oriol over 8 years
    @Lilleman I remember reading that a certain version of Firefox had the improvement of detecting when all keys in a map are strings, then it could be optimized more (like objects). So I guess maps will be slower on browsers without this kind of optimization. But that's implementation-dependent, and I haven't done any test to measure performance.
  • Lilleman
    Lilleman over 8 years
    @Oriol Cool. I'm imagining the key always just being a pointer to a more advanced object anyways, so it should, in my head, always be optimized. In theory. I'll research this a bit I think. :)
  • Xplouder
    Xplouder about 8 years
    Map > Object in performance scope - jsperf.com/es6-map-vs-object-properties/94
  • Oriol
    Oriol about 8 years
    @Xplouder That test uses expensive hasOwnProperty. Without that, Firefox iterates objects much faster than maps. Maps are still faster on Chrome, though. jsperf.com/es6-map-vs-object-properties/95
  • Xplouder
    Xplouder about 8 years
    True, seems that Firefox 45v iterates objects away faster than Chrome +49v. However Maps still wins vs objects in Chrome.
  • 4thex
    4thex over 5 years
    I am a little late to the party, but I had a similar need, and I was able to do this: JSON.stringify(Array.from(myMap.entries()))
  • Sat Thiru
    Sat Thiru about 5 years
    This does not convert to a JSON object, but instead to an Array of arrays. Not the same thing. See Evan Carroll's answer below for a more complete answer.
  • Bergi
    Bergi about 5 years
    @SatThiru An array of tuples is the customary representation of Maps, it goes well with the constructor and iterator. Also it is the only sensible representation of maps that have non-string keys, and object would not work there.
  • Sat Thiru
    Sat Thiru about 5 years
    Bergi, please note that OP said "My keys are guaranteed to be strings".
  • Bergi
    Bergi about 5 years
  • bvdb
    bvdb over 4 years
    A Map uses buckets to organize its keys. So, it should be faster than a simple property, especially when you have 1000 of them.
  • Lonnie Best
    Lonnie Best over 4 years
    Without testing your answer, I already appreciate how it brings attention to the problem of nested Maps. Even if you successfully convert this to JSON, any parsing done in the future has to have explicit awareness that the JSON was originally a Map and (even worse) that each sub-map (it contains) was also originally a map. Otherwise, there's no way to be sure that an array of pairs isn't just intended to be exactly that, instead of a Map. Hierarchies of objects and arrays do not carry this burden when parsed. Any proper serialization of Map would explicitly indicate that it is a Map.
  • Lonnie Best
    Lonnie Best over 4 years
    More about that here.
  • Drenai
    Drenai about 4 years
    @Bergi Stringify doesn't work if the key is an object e.g. "{"[object Object]":{"b":2}}" - object keys being one of the main features of Maps
  • Bergi
    Bergi about 4 years
    @Drenai Then don't use Obect.fromEntries, and use the code from my main answer instead of the one from the comment. The code that builds an object literal was in response to Sat Thiru, who gave the case that the keys are strings.
  • vasia
    vasia about 4 years
    I'm not sure that many will be satisfied with extending the core map class just to serialize it to a json...
  • Cody
    Cody about 4 years
    They don't have to be, but it's a more SOLID way of doing it. Specifically, this aligns with the LSP and OCP principles of SOLID. That is, the native Map is being extended, not modified, and one can still use Liskov Substitution (LSP) with a native Map. Granted, it's more OOP than a lot of novices or staunch Functional Programming people would prefer, but at least it's beset upon a tried & true baseline of fundamental software design principles. If you wanted to implement Interface Segregation Principle (ISP) of SOLID, you can have a small IJSONAble interface (using TypeScript, of course).
  • mattsven
    mattsven about 4 years
    This'll work for a one-dimensional Map, but not for an n-dimensional map.
  • napolux
    napolux almost 4 years
    Just passing by and figure out my problem thanks to this. I really wish to move to a farm and leave all this behind, sometimes.
  • rynop
    rynop over 3 years
    Just marked this as correct. While I don't like the fact you have to "dirty up" the data across the wire with a non-standardized dataType, I can't think of a cleaner way. Thanks.
  • Pawel
    Pawel over 3 years
    @rynop yeah it's more like a little program with it's own data storage format than just using pure native functionality
  • JimiDini
    JimiDini over 3 years
    @Pawel what is the reason for using this[key] instead of value?
  • Pawel
    Pawel over 3 years
    @JimiDini good point, updated. Now if someone wants to declare these as arrow functions it won't mess with the scope
  • Procrastin8
    Procrastin8 almost 3 years
    This one is nice but does require you to target ES2019.
  • mkoe
    mkoe almost 3 years
    To me there seems to be a slight problem: any ordinary object o which by chance has the property o.dataType==='Map' will also be converted to a Map when you serialize-deserialize it.
  • Pawel
    Pawel over 2 years
    @mkoe sure, but the probability of that is somewhere between being hit by a lightning and being hit by a lightning while hiding in a basement
  • Admin
    Admin over 2 years
    Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
  • TylerH
    TylerH over 2 years
    This sounds like a nice 10,000 foot process overview, but an actual implementation would be much more useful.
  • Cbaskey.it
    Cbaskey.it over 2 years
    Well, it was more food for thought really than a total solution. Sorry I'm new here and not sure how to add my code to the comments as yet.
  • Steven Shi
    Steven Shi over 2 years
    I don't get, why this is related to the question?
  • roim
    roim over 2 years
    > Going to an JavaScript Object < Shouldn't it have code to handle keys such as __proto__? Or you can damage the entire environment by trying to serialize such a map. Alok's response doesn't suffer from this, I believe.
  • asp47
    asp47 over 2 years
    Warning: Map can have non-string values as keys. This will not work if your Map keys are non-stringify-able types themselves : JSON.stringify(Object.fromEntries(new Map([['s', 'r'],[{s:3},'g']]))) becomes '{"s":"r","[object Object]":"g"}'
  • Megajin
    Megajin about 2 years
    If anyone is tempted to use Object.fromEntries() as stated in the MDN Docs I highly advise against it. You can parse a Map to an Object but not back! It will throw a object is not iterable error.
  • Megajin
    Megajin about 2 years
    Careful, this is just good if you want to go one way.Object.fromEntries() as stated in the MDN Docs you can parse a Map to an Object but not back! It will throw a object is not iterable error.
  • Alok Somani
    Alok Somani about 2 years
    @Megajin Object.fromEntries() is non-destructive, so you will still have your original Map intact.
  • Megajin
    Megajin about 2 years
    @AlokSomani yes, you are right. However if you want to parse a JSON (or the newly created Object) back, it won't work.