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?
20 Answers
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);
-
14Just 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.– rynopCommented Oct 9, 2020 at 18:07 -
3@Pawel what is the reason for using
this[key]
instead ofvalue
?– JimiDiniCommented Jan 21, 2021 at 14:59 -
2To 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.– mkoeCommented Jul 15, 2021 at 19:42
-
4@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– PawelCommented Sep 3, 2021 at 13:40
-
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));
-
18This 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. Commented May 8, 2019 at 14:52
-
13@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.– BergiCommented May 8, 2019 at 16:42 -
Bergi, please note that OP said "My keys are guaranteed to be strings". Commented May 10, 2019 at 17:20
-
14@SatThiru In that case, use
JSON.stringify(Object.fromEntries(map.entries()))
andnew Map(Object.entries(JSON.parse(jsonText)))
– BergiCommented May 10, 2019 at 17:43 -
6@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.– BergiCommented Mar 13, 2020 at 18:22
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.
-
57for the curious-in the latest chrome, any map serializes into '{}'– CapajCommented Jan 6, 2016 at 16:20
-
14"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. :)– LillemanCommented Feb 11, 2016 at 18:01
-
3@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– OriolCommented Mar 18, 2016 at 13:49 -
6Just passing by and figure out my problem thanks to this. I really wish to move to a farm and leave all this behind, sometimes.– napoluxCommented Aug 10, 2020 at 14:38
-
3While this answer definitely points out the tricky bits, it most certainly is not "impossible" as the accepted answer demonstrates. Commented Jul 28, 2022 at 15:52
While there is no method provided by ecmascript yet, this can still be done using JSON.stringify
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"]]'
-
1> 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.– roimCommented Oct 29, 2021 at 23:40 -
As pointed out in Oriol's answer, this is incorrect. Map keys can be objects, which this answer doesn't handle. Commented Jul 28, 2022 at 15:55
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));
-
16This'll work for a one-dimensional Map, but not for an n-dimensional map.– mattsvenCommented May 12, 2020 at 19:01
-
Given your example is a simple use case in which keys are going to be simple types, I think this is the easiest way to JSON stringify a Map.
JSON.stringify(Object.fromEntries(map));
The way I think about the underlying data structure of a Map is as an array of key-value pairs (as arrays themselves). So, something like this:
const myMap = new Map([
["key1", "value1"],
["key2", "value2"],
["key3", "value3"]
]);
Because that underlying data structure is what we find in Object.entries, we can utilize the native JavaScript method of Object.fromEntries()
on a Map as we would on an Array:
Object.fromEntries(myMap);
/*
{
key1: "value1",
key2: "value2",
key3: "value3"
}
*/
And then all you're left with is using JSON.stringify() on the result of that.
-
This one is nice but does require you to target ES2019. Commented Jun 23, 2021 at 11:36
-
1
-
@Megajin
Object.fromEntries()
is non-destructive, so you will still have your original Map intact. Commented Apr 6, 2022 at 7:35 -
1@AlokSomani yes, you are right. However if you want to parse a JSON (or the newly created Object) back, it won't work.– MegajinCommented Apr 6, 2022 at 12:12
-
1Just for additional information, this is totally ok for Typescript users which are able to make sure that the whole map structure (meaning nested objects as well) have string keys
Map<string, otherType>
Commented Mar 24, 2023 at 14:02
Correctly round-tripping serialization
Just copy this and use it. Or use the npm package.
const serialize = (value) => JSON.stringify(value, stringifyReplacer);
const deserialize = (text) => JSON.parse(text, parseReviver);
// License: CC0
function stringifyReplacer(key, value) {
if (typeof value === "object" && value !== null) {
if (value instanceof Map) {
return {
_meta: { type: "map" },
value: Array.from(value.entries()),
};
} else if (value instanceof Set) { // bonus feature!
return {
_meta: { type: "set" },
value: Array.from(value.values()),
};
} else if ("_meta" in value) {
// Escape "_meta" properties
return {
...value,
_meta: {
type: "escaped-meta",
value: value["_meta"],
},
};
}
}
return value;
}
function parseReviver(key, value) {
if (typeof value === "object" && value !== null) {
if ("_meta" in value) {
if (value._meta.type === "map") {
return new Map(value.value);
} else if (value._meta.type === "set") {
return new Set(value.value);
} else if (value._meta.type === "escaped-meta") {
// Un-escape the "_meta" property
return {
...value,
_meta: value._meta.value,
};
} else {
console.warn("Unexpected meta", value._meta);
}
}
}
return value;
}
Performance?
There is a version that is equally high quality, but has better performance (tested in Chrome and Firefox). If that matters to you, then please check it out!
https://stackoverflow.com/a/79016027/3492994
Why is this hard?
It should be possible to input any kind of data, get valid JSON, and from there correctly reconstruct the input.
This means dealing with
- Maps that have objects as keys
new Map([ [{cat:1}, "value"] ])
. This means that any answer which usesObject.fromEntries
is probably wrong. - Maps that have nested maps
new Map([ ["key", new Map([ ["nested key", "nested value"] ])] ])
. A lot of answers sidestep this by only answering the question and not dealing with anything beyond that. - Mixing objects and maps
{"key": new Map([ ["nested key", "nested value"] ]) }
.
and on top of those difficulties, the serialisation format must be unambiguous. Otherwise one cannot always reconstruct the input. The top answer has one failing test case, see below.
Hence, I wrote this improved version. It uses _meta
instead of dataType
, to make conflicts rarer and if a conflict does happen, it actually unambiguously handles it. Hopefully the code is also simple enough to easily be extended to handle other containers.
My answer does, however, not attempt to handle exceedingly cursed cases, such as a map with object properties.
A test case for my answer, which demonstrates a few edge cases
const originalValue = [
new Map([['a', {
b: {
_meta: { __meta: "cat" },
c: new Map([['d', 'text']])
}
}]]),
{ _meta: { type: "map" }}
];
console.log(originalValue);
let text = JSON.stringify(originalValue, stringifyReplacer);
console.log(text);
console.log(JSON.parse(text, parseReviver));
Accepted answer not round-tripping
The accepted answer is really lovely. However, it does not round trip when an object with a dataType
property is passed it it. That can make it dangerous to use in certain circumstances, such as
JSON.stringify(data, acceptedAnswerReplacer)
and send it over the network.- Naive network handler automatically JSON decodes it. From this point on forth, you cannot safely use the accepted answer with the decoded data, since doing so would cause lots of sneaky issues.
This answer uses a slightly more complex scheme to fix such issues.
// Test case for the accepted answer
const originalValue = { dataType: "Map" };
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, str, newValue);
// > Object { dataType: "Map" } , Map(0)
// Notice how the input was changed into something different
A Better Solution
// somewhere...
class Klass extends Map {
toJSON() {
var object = { };
for (let [key, value] of this) object[key] = value;
return object;
}
}
// somewhere else...
import { Klass as Map } from '@core/utilities/ds/map'; // <--wherever "somewhere" is
var map = new Map();
map.set('a', 1);
map.set('b', { datum: true });
map.set('c', [ 1,2,3 ]);
map.set( 'd', new Map([ ['e', true] ]) );
var json = JSON.stringify(map, null, '\t');
console.log('>', json);
Output
> {
"a": 1,
"b": {
"datum": true
},
"c": [
1,
2,
3
],
"d": {
"e": true
}
}
Hope that is less cringey than the answers above.
-
1I'm not sure that many will be satisfied with extending the core map class just to serialize it to a json...– vasiaCommented Apr 5, 2020 at 21:36
-
4They 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).– CodyCommented Apr 5, 2020 at 22:47 -
2Yes, this is a good solution since it can produce specific serialisation variations. You can do
new MyMap1(someMap)
ornew MyMap2(someMap)
to clone and extend exiting maps easily. And thenMyMap1
andMyMap2
can have different serialisation. For example, one might just serialise to a list of tuples, the other to an object. Furthermore, there can be special handling for serialising keys (since those could be arbitrary objects) or similar. Thereplacer
option inJSON.stringify
works OK but if it has to handle multiple types, it becomes cumbersome.– VLAZCommented Feb 19, 2024 at 9:03
Stringify a Map
instance (objects as keys are OK):
JSON.stringify([...map])
or
JSON.stringify(Array.from(map))
or
JSON.stringify(Array.from(map.entries()))
output format:
// [["key1","value1"],["key2","value2"]]
Just want to share my version for both Map and Set JSON.stringify only. I'm sorting them, useful for debugging...
function replacer(key, value) {
if (value instanceof Map) {
const reducer = (obj, mapKey) => {
obj[mapKey] = value.get(mapKey);
return obj;
};
return [...value.keys()].sort().reduce(reducer, {});
} else if (value instanceof Set) {
return [...value].sort();
}
return value;
}
Usage:
const map = new Map();
const numbers= new Set()
numbers.add(3);
numbers.add(2);
numbers.add(3);
numbers.add(1);
const chars= new Set()
chars.add('b')
chars.add('a')
chars.add('a')
map.set("numbers",numbers)
map.set("chars",chars)
console.log(JSON.stringify(map, replacer, 2));
Result:
{
"chars": [
"a",
"b"
],
"numbers": [
1,
2,
3
]
}
-
This replacer function is the only thing that worked for me. Thanks! Commented Jun 25, 2024 at 11:15
The very simple way.
const map = new Map();
map.set('Key1', "Value1");
map.set('Key2', "Value2");
console.log(Object.fromEntries(map));
` Output:-
{"Key1": "Value1","Key2": "Value2"}
-
7Warning: 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"}'
– asp47Commented Jan 5, 2022 at 22:09
Below solution works even if you have nested Maps
function stringifyMap(myMap) {
function selfIterator(map) {
return Array.from(map).reduce((acc, [key, value]) => {
if (value instanceof Map) {
acc[key] = selfIterator(value);
} else {
acc[key] = value;
}
return acc;
}, {})
}
const res = selfIterator(myMap)
return JSON.stringify(res);
}
-
1Without 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
. Commented Nov 30, 2019 at 6:53 -
You cannot call JSON.stringify
on Map
or Set
.
You will need to convert:
- the
Map
into a primitiveObject
, usingObject.fromEntries
, or - the
Set
into a primitiveArray
, using the spread operator[...]
…before calling JSON.stringify
Map
const
obj = { 'Key1': 'Value1', 'Key2': 'Value2' },
map = new Map(Object.entries(obj));
map.set('Key3', 'Value3'); // Add a new entry
// Does NOT show the key-value pairs
console.log('Map:', JSON.stringify(map));
// Shows the key-value pairs
console.log(JSON.stringify(Object.fromEntries(map), null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }
Set
const
arr = ['Value1', 'Value2'],
set = new Set(arr);
set.add('Value3'); // Add a new item
// Does NOT show the values
console.log('Set:', JSON.stringify(set));
// Show the values
console.log(JSON.stringify([...set], null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }
toJSON method
If you want to call JSON.stringify
on a class object, you will need to override the toJSON
method to return your instance data.
class Cat {
constructor(options = {}) {
this.name = options.name ?? '';
this.age = options.age ?? 0;
}
toString() {
return `[Cat name="${this.name}", age="${this.age}"]`
}
toJSON() {
return { name: this.name, age: this.age };
}
static fromObject(obj) {
const { name, age } = obj ?? {};
return new Cat({ name, age });
}
}
/*
* JSON Set adds the missing methods:
* - toJSON
* - toString
*/
class JSONSet extends Set {
constructor(values) {
super(values)
}
toString() {
return super
.toString()
.replace(']', ` ${[...this].map(v => v.toString())
.join(', ')}]`);
}
toJSON() {
return [...this];
}
}
const cats = new JSONSet([
Cat.fromObject({ name: 'Furball', age: 2 }),
Cat.fromObject({ name: 'Artemis', age: 5 })
]);
console.log(cats.toString());
console.log(JSON.stringify(cats, null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }
-
2There's a much easier way of serializing Maps and Sets to JSON, since JSON.stringify and JSON.parse have a second argument that lets you add custom rules. See my answer for an answer that correctly round-trips in all cases. Commented Jan 23, 2023 at 15:09
The following method will convert a Map to a JSON string:
public static getJSONObj(): string {
return JSON.stringify(Object.fromEntries(map));
}
Example:
const x = new Map();
x.set("SomeBool", true);
x.set("number1", 1);
x.set("anObj", { name: "joe", age: 22, isAlive: true });
const json = getJSONObj(x);
// Output:
// '{"SomeBool":true,"number1":1,"anObj":{"name":"joe","age":222,"isAlive":true}}'
-
This is the right answer unless I am missing something. All these other methods are making an absolute meal out of this.– MSOACCCommented Jan 10, 2023 at 14:03
-
1This answer adds nothing new compared to Alok Somani's answer. Plus, it does not handle nested maps. It also has the same bug as Rakesh Singh Balhara's answer. Commented Jan 23, 2023 at 15:05
Even Better than Correctly round-tripping serialization
The top-voted answer is not really the correct one, as has been already described in detail by Stefnotch in their answer here.
I'm providing my own drop-in solution that can be easily copy-pasted into any project (and can be used freely with a reference to this answer somewhere in the comments):
/**
* Provides a `JSON.stringify` replacer function that supports Map and Set object serialization.
*
* This replacer function fully supports Map and Set nesting as well as mixed values (including
* regular objects) as opposed to the popular solution recommended at MDN and described in more
* detail here: https://stackoverflow.com/q/29085197.
*
* Map and Set objects are serialized as plain JSON arrays where the first element is a special
* token string (`@Map` or `@Set`, respectively), and all subsequent elements are either `[key,
* value]` arrays (with the Map contents) or just `value` sequences (with the Set contents). To
* avoid ambiguity, regular arrays with the first element equal to one of the token strings will
* have the `@Array` token string prepended to their contents when serialized.
*
* The reverse #jsonMapSetReviver function will undo these operations when parsing the resulting
* JSON data, so it must always be used when deserializing data serialized with #jsonMapSetReplacer
* (even if there are no Map or Set objects, because there may be arrays with `@Array` prepended).
*
* This implementation seems to be faster than other alternatives almost in all cases (taking its
* functionality into account). It is also published at https://stackoverflow.com/a/79016027/3579458
* (there are some tests).
*/
function jsonMapSetReplacer (_, value_)
{
if (typeof (value_) === 'object')
{
if (value_ instanceof Map)
{
value_ = Array.from (value_);
value_.unshift ('@Map');
}
else if (value_ instanceof Set)
{
value_ = Array.from (value_);
value_.unshift ('@Set');
}
else if (Array.isArray (value_) && value_.length > 0 &&
(value_ [0] === '@Map' || value_ [0] === '@Set' || value_ [0] === '@Array'))
{
value_ = value_.slice ();
value_.unshift ('@Array');
}
}
return value_;
}
/**
* Provides a `JSON.parse` reviver function that supports Map and Set object deserialization.
*
* Must be used to deserialize JSON data serialized using #jsonMapSetReplacer.
*/
function jsonMapSetReviver (_, value_)
{
if (Array.isArray (value_) && value_.length > 0)
{
let isMap, isSet;
if ((isMap = value_ [0] === '@Map') || (isSet = value_ [0] === '@Set') || value_ [0] === '@Array')
{
value_.shift ();
if (isMap)
value_ = new Map (value_);
else if (isSet)
value_ = new Set (value_);
}
}
return value_;
}
This solution has the following benefits over other variants presented here:
It supports nested
Map
andSet
objects and should properly support all kinds of mixing (maps with objects, objects with maps etc).It doesn't use relatively "heavy" object machinery to encapsulate
Map
andSet
(and yet it protects from possible conflicts in values matching encapsulation tags) — just plain arrays. This should make it faster both to serialize and deserialize (some tests, "Array Based 2" is the latest version, "Map Based" is Stefnotch's solution).It doesn't use the spread operator that is known to be much slower than other primitives when it's needed to prepend items to the beginning of the array (look for tests in the comments to other answers) — it only uses
slice
,shift
andunshift
modifying the original array in place where possible instead of creating a copy (e.g. when deserializing). This should also give some speed boost (and use memory more efficiently), especially on large data sets.The output overhead due to encapsulation and escaping should be very small — it is used only when absolutely necessary (and it will be just a single additional short array element in all cases). For non-
Map
and non-Set
objects and for almost all "normal" arrays there will be no overhead at all, compared to the standard JSON serialization.
UPDATE
I removed support for serializing undefined
values. I found out that JSON.parse
doesn't play well with undefined
in arrays: when it gets undefined
from the reviver, it simply deletes the respective element (effectively creating a hole) instead of just storing undefined
there. Not what one wants.
-
1I can confirm that this is indeed faster than my solution when tested on Firefox and Chrome jsperf.app/cojiwe . It's faster, despite being O(n) (slice copies an array, and "shift" and "unshift" are among the slowest array operations). Commented Sep 24, 2024 at 8:54
-
1@Stefnotch no, would would it fail? Your evil_array works just fine here. It will be prepended with a single
@Array
that will then get removed from it when deserializing, giving back the same["@Set", "@Set"]
. Regardingshift
andunshift
. Slowest compared to what? If we compare them to other array mutating primitives, they are the fastest to my knowledge (and memory efficient as they don't create copies). However, your comment gave me an idea of how to make it even better by completely avoiding array mutation, both instringify
andparse
. I will check that, thanks!– truedmikCommented Sep 24, 2024 at 12:35 -
1My bad, I messed up my testing code. Let me remove the wrong comment then. :( As for
shift
andunshift
, they are slow because they need to move all subsequent array elements. They're slow compared topush
andpop
. Commented Sep 24, 2024 at 16:58 -
1Okay, I didn't put it clearly. By "array modification primitives" I meant specifically ones that prepend to the array (of course
push
andpop
are faster than anything else since no memory is moved, just the end-of-array pointer is set to a new value - when there is still some preallocated space, at least). I've also removedundefined
support as it turns out to be useless (see the UPDATE section) and made code even shorter a bit. It became a tiny bit faster too, check jsperf.app/cojiwe/5 (although it seems to be within the margin of error).– truedmikCommented Sep 24, 2024 at 19:20 -
1I tried it out with iterators, but unsurprisingly those are much slower than copying and mutating an array jsperf.app/cojiwe/8 . Too bad Javascript does not have zero-extra-cost abstractions. This means that your answer is the fastest solution that we can currently come up with. Time to edit my answer and point people to this version :) Commented Sep 25, 2024 at 9:22
JSON.stringify(errorCodes, (_, value) =>
value instanceof Map || value instanceof Set ? Array.from(value) : value
);
-
1It will work correctly with nested Maps because if a Map is converted into an Array, the Array members (including nested Maps) will be handled by the same logic. Commented Apr 23, 2024 at 7:25
-
This answer is very simple, but could be improved with an explanation. It also is only applicable when round-tripping is not a requirement, as there is no unambiguous way of
JSON.parse()
ing the result. Commented Apr 23, 2024 at 13:39
Whatever you do, don't do this.
Map.prototype.toJSON = function() {
return Object.fromEntries(this);
};
const map = new Map();
map.set('a', 1);
map.set('b', { datum: true });
map.set(false, [1, 2, 3]);
map.set('d', new Map([['e', true]]) );
const json = JSON.stringify(map);
console.log(json);
-
1
-
This is missing a few essential parts of an answer: Why should one not do this (don't override the prototype, and don't use Object.fromEntries), and what should one do instead? Commented Feb 7 at 15:18
I really don't know why there are so many long awesers here. This short version solved my problem:
const data = new Map()
data.set('visible', true)
data.set('child', new Map())
data.get('child').set('visible', false)
const str = JSON.stringify(data, (_, v) => v instanceof Map ? Object.fromEntries(v) : v)
// '{"visible":true,"child":{"visible":false}}'
const recovered = JSON.parse(str, (_, v) => typeof v === 'object' ? new Map(Object.entries(v)) : v)
// Map(2) { 'visible' => true, 'child' => Map(1) { 'visible' => false } }
-
1This turns objects into maps, which isn't what one wants. Try it with
const data = {visible: true, child: {visible: false}}
. You'll recover two nested maps instead of recovering objects. Check out my answer for an actually correct implementation. Commented Jan 23, 2023 at 15:03
Recursive JSON stringify for MAP
You can use the second argument "replacer" of JSON.stringify
:
JSON.stringify(value, replacer)
replacer (Optional) :
- A function that alters the behavior of the stringification process, or an array of strings and numbers that specifies properties of value to be included in the output.
[Read more on mdn...]
If you detect a map with instanceof Map
, you will apply Object.fromEntries(value)
like this :
function JSON_MapStringify(obj) {
return JSON.stringify(obj, function (key, value) {
if (value instanceof Map) {
return Object.fromEntries(value)
}
return value
})
}
Demo
For:
{
"foo": Map (1) {
"o": Map(1) {
"k": 2
}
},
"bar": 1
}
See the result:
default (JSON.stringify) : {"foo":{},"bar":1}
custom (JSON_MapStringify): {"foo":{"o":{"k":2}},"bar":1}
const sub = new Map()
sub.set('k', 2)
const main = new Map()
main.set('o', sub)
const obj = { foo: main, bar: 1}
console.log('default (JSON.stringify) :', JSON.stringify(obj))
function JSON_MapStringify(obj) {
return JSON.stringify(obj, function (key, value) {
if (value instanceof Map) {
return Object.fromEntries(value)
}
return value
})
}
console.log('custom (JSON_MapStringify):', JSON_MapStringify(obj))
-
-
1Beware that this does not handle objects being keys of a map.
JSON_MapStringify(new Map([[{a:"foo"}, "bar"]]))
returns'{"[object Object]":"bar"}'
Commented Feb 7 at 14:38
Although there would be some scenarios where if you were the creator of the map you would write your code in a separate 'src' file and save a copy as a .txt file and, if written concisely enough, could easily be read in, deciphered, and added to server-side.
The new file would then be saved as a .js and a reference to it sent back from the server. The file would then reconstruct itself perfectly once read back in as JS. The beauty being that no hacky iterating or parsing is required for reconstruction.
-
1Your 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.– Community BotCommented Oct 20, 2021 at 13:43
-
2This sounds like a nice 10,000 foot process overview, but an actual implementation would be much more useful.– TylerHCommented Oct 20, 2021 at 15:51
-
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. Commented Oct 21, 2021 at 3:35
-
1
[...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] */ }`, '')
obj[key]
may get you something unexpected. Consider the caseif (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);
.