How do you JSON.stringify an ES6 Map?
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));
Related videos on Youtube
Comments
-
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()
aMap
. 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 about 6 yearsinteresting article on the topic 2ality.com/2015/08/es6-map-json.html
-
PatS about 6 yearsI 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 about 6 yearsIf 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 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 caseif (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);
.
-
-
Capaj over 8 yearsfor the curious-in the latest chrome, any map serializes into '{}'
-
Oriol over 8 yearsI have explained here what exactly I meant when I said "you can't".
-
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 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 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 about 8 yearsMap > Object in performance scope - jsperf.com/es6-map-vs-object-properties/94
-
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 about 8 yearsTrue, seems that Firefox 45v iterates objects away faster than Chrome +49v. However Maps still wins vs objects in Chrome.
-
4thex over 5 yearsI 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 about 5 yearsThis 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 about 5 years@SatThiru An array of tuples is the customary representation of
Map
s, 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 about 5 yearsBergi, please note that OP said "My keys are guaranteed to be strings".
-
Bergi about 5 years@SatThiru In that case, use
JSON.stringify(Object.fromEntries(map.entries()))
andnew Map(Object.entries(JSON.parse(jsonText)))
-
bvdb over 4 yearsA
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 over 4 yearsWithout 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 anarray 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 ofMap
would explicitly indicate that it is aMap
. -
Lonnie Best over 4 yearsMore about that here.
-
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 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 about 4 yearsI'm not sure that many will be satisfied with extending the core map class just to serialize it to a json...
-
Cody about 4 yearsThey 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 about 4 yearsThis'll work for a one-dimensional Map, but not for an n-dimensional map.
-
napolux almost 4 yearsJust 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 over 3 yearsJust 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 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 over 3 years@Pawel what is the reason for using
this[key]
instead ofvalue
? -
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 almost 3 yearsThis one is nice but does require you to target ES2019.
-
mkoe almost 3 yearsTo 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 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 over 2 yearsYour 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 over 2 yearsThis sounds like a nice 10,000 foot process overview, but an actual implementation would be much more useful.
-
Cbaskey.it over 2 yearsWell, 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 over 2 yearsI don't get, why this is related to the question?
-
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 over 2 yearsWarning: 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 about 2 yearsIf anyone is tempted to use
Object.fromEntries()
as stated in the MDN Docs I highly advise against it. You can parse aMap
to anObject
but not back! It will throw aobject is not iterable
error. -
Megajin about 2 yearsCareful, this is just good if you want to go one way.
Object.fromEntries()
as stated in the MDN Docs you can parse aMap
to anObject
but not back! It will throw aobject is not iterable
error. -
Alok Somani about 2 years@Megajin
Object.fromEntries()
is non-destructive, so you will still have your original Map intact. -
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.