354

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?

zhulien
  • 5,145
  • 3
  • 22
  • 36
rynop
  • 50,086
  • 26
  • 101
  • 112
  • 6
    interesting article on the topic http://2ality.com/2015/08/es6-map-json.html – David Chase Apr 04 '18 at 15:58
  • I was able to get this to work. The results are on Plunkr at http://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. – PatS Apr 10 '18 at 22:05
  • 2
    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] */ }\`, '')` – user56reinstatemonica8 May 10 '18 at 08:42
  • @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);`. – Franklin Yu Nov 20 '18 at 15:20

16 Answers16

395

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);
Pawel
  • 16,093
  • 5
  • 70
  • 73
  • 12
    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. – rynop Oct 09 '20 at 18:07
  • @rynop yeah it's more like a little program with it's own data storage format than just using pure native functionality – Pawel Oct 09 '20 at 21:52
  • 2
    @Pawel what is the reason for using `this[key]` instead of `value`? – JimiDini Jan 21 '21 at 14:59
  • 1
    @JimiDini good point, updated. Now if someone wants to declare these as arrow functions it won't mess with the scope – Pawel Jan 27 '21 at 09:35
  • 1
    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. – mkoe Jul 15 '21 at 19:42
  • 3
    @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 – Pawel Sep 03 '21 at 13:40
  • 1
    If anyone is tempted to use `Object.fromEntries()` as stated in the [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) 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 Mar 21 '22 at 13:49
  • 1
    @mkoe I wrote an answer which actually fixes that problem. – Stefnotch Jul 28 '22 at 16:52
  • There is a misplaced comma in the replacer function: "value: Array.from(value.entries())," – F.K. Juliano Oct 18 '22 at 16:06
  • 1
    @F.K.Juliano this is a "dangling comma" which is there at purpose for git diffs – Pawel Oct 18 '22 at 16:12
  • You actually don't need to map.entries in the replacer, you can just array.from(map). – duttaoindril Jan 22 '23 at 18:41
  • 1
    @Stefnotch this could be exploited this way, but what's preventing anyone from changing any other data types in a payload i.e. [] to {} ? To prevent this validation is required and when a payload is validated it's happening after parsing so it becomes pretty clear that the field is not of object but map type – Pawel Mar 01 '23 at 00:53
  • It still does remain a footgun as is IMO. And there's apparently another sneaky case where it breaks. If one has some combination of the normal JSON `parse`, `stringify` and the `customParse` and `customStringify` from this answer, then one has to be really careful about how to apply them. Breakage could happen in a complex enough app with a backend and frontend. That is, `customParse(customStringify( data ))` works. So does `customParse(stringify(parse(customStringify( data ))))`. But `customParse(stringify(customParse(customStringify(parse(customStringify( data )))))))` will break. – Stefnotch Mar 02 '23 at 06:34
  • Which raises the question "how would that happen". Here's one scenario: 1. customStringify( data ) and send it over the network 2. naive network handler automatically JSON decodes it 3. then we forward it, so we naively JSON encode it 4. customParse( data ) – Stefnotch Mar 02 '23 at 06:44
  • It is entirely possible that I just worry too much about correctness though. Either way, I would prefer it if people copied bulletproof code that they can rely on. As such, I am absolutely allowing and encouraging you to tweak your answer to actually handle that case, or to just copy the code from my answer. :) – Stefnotch Mar 02 '23 at 06:49
  • 1
    @Stefnotch I agree that there could be a place that lets this slip through. i.e. a internal company npm package which is a wrapper for the api to pass some default options for auth headers and automatically parse JSON for convenience i.e. `const getUrl = (url) => fetch(url, apiOptions).then(res => res.json())` where `apiOptions` have some global settings. It's worth remembering that such cases may happen and these must be adapted correctly – Pawel Mar 02 '23 at 18:07
  • @Pawel In that case, may I edit your lovely answer to include a more bulletproof way of handling such cases? – Stefnotch Mar 02 '23 at 18:32
  • @Stefnotch that sounds like an architectural choice for a whole app so I don't see how a code change in this elementary transformation could handle this. Feel free to propse the change, although I find this answer simple and complete enough and reserve the right to undo the change if it makes the solution too complicated or the answer too verbose – Pawel Mar 02 '23 at 18:46
  • @Pawel I submitted my edit, it makes the code slightly more complex, but should handle everything in a pretty bulletproof way. Let me know what you think. (And then we can clean up those comments here.) – Stefnotch Mar 03 '23 at 12:26
  • @Stefnotch thanks for trying, but it does make the answer much harder to understand. The best will be if you post the modified version as a new answer – Pawel Mar 03 '23 at 14:38
  • @Pawel That's what I did down there :) – Stefnotch Mar 03 '23 at 14:49
149

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));
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 15
    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. – Sat Thiru May 08 '19 at 14:52
  • 11
    @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. – Bergi May 08 '19 at 16:42
  • Bergi, please note that OP said "My keys are guaranteed to be strings". – Sat Thiru May 10 '19 at 17:20
  • 11
    @SatThiru In that case, use [`JSON.stringify(Object.fromEntries(map.entries()))`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) and [`new Map(Object.entries(JSON.parse(jsonText)))`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) – Bergi May 10 '19 at 17:43
  • 1
    @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 – Drenai Mar 13 '20 at 14:46
  • 5
    @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. – Bergi Mar 13 '20 at 18:22
71

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.
rynop
  • 50,086
  • 26
  • 101
  • 112
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • 52
    for the curious-in the latest chrome, any map serializes into '{}' – Capaj Jan 06 '16 at 16:20
  • I have explained [here](http://stackoverflow.com/a/35078054/1529630) what exactly I meant when I said "you can't". – Oriol Jan 29 '16 at 06: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. :) – Lilleman Feb 11 '16 at 18:01
  • @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. – Oriol Feb 11 '16 at 18:04
  • @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. :) – Lilleman Feb 11 '16 at 18:13
  • Map > Object in performance scope - https://jsperf.com/es6-map-vs-object-properties/94 – Xplouder Mar 18 '16 at 02:32
  • 3
    @Xplouder That test uses expensive `hasOwnProperty`. Without that, Firefox iterates objects much faster than maps. Maps are still faster on Chrome, though. http://jsperf.com/es6-map-vs-object-properties/95 – Oriol Mar 18 '16 at 13:49
  • 2
    True, seems that Firefox 45v iterates objects away faster than Chrome +49v. However Maps still wins vs objects in Chrome. – Xplouder Mar 18 '16 at 19:52
  • 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()))` – 4thex Sep 28 '18 at 21:08
  • A `Map` uses buckets to organize its keys. So, it should be faster than a simple property, especially when you have 1000 of them. – bvdb Nov 06 '19 at 09:05
  • 5
    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. – napolux Aug 10 '20 at 14:38
  • 3
    While this answer definitely points out the tricky bits, it most certainly is not "impossible" as the accepted answer demonstrates. – Stefnotch Jul 28 '22 at 15:52
  • @Stefnotch https://imgur.com/a/cba5XXO – Andrew Feb 02 '23 at 22:05
44

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"]]'
Anbuselvan Rocky
  • 606
  • 6
  • 22
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • 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. – roim Oct 29 '21 at 23:40
  • As pointed out in Oriol's answer, this is incorrect. Map keys can be objects, which this answer doesn't handle. – Stefnotch Jul 28 '22 at 15:55
34

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));
metodribic
  • 1,561
  • 17
  • 27
27

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.

Alok Somani
  • 279
  • 3
  • 3
  • This one is nice but does require you to target ES2019. – Procrastin8 Jun 23 '21 at 11:36
  • 1
    Careful, this is just good if you want to go one way.`Object.fromEntries()` as stated in the [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) you can parse a `Map` to an `Object` but not back! It will throw a `object is not iterable` error. – Megajin Mar 21 '22 at 13:53
  • @Megajin `Object.fromEntries()` is non-destructive, so you will still have your original Map intact. – Alok Somani Apr 06 '22 at 07: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. – Megajin Apr 06 '22 at 12:12
  • Just 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` – Laercio Metzner Mar 24 '23 at 14:02
14

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.

Cody
  • 9,785
  • 4
  • 61
  • 46
  • I'm not sure that many will be satisfied with extending the core map class just to serialize it to a json... – vasia Apr 05 '20 at 21:36
  • 3
    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). – Cody Apr 05 '20 at 22:47
12

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"]]
am0wa
  • 7,724
  • 3
  • 38
  • 33
10

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;
}

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 uses Object.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

  1. JSON.stringify(data, acceptedAnswerReplacer) and send it over the network.
  2. 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
Stefnotch
  • 511
  • 2
  • 13
  • 26
5

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"}

  • 6
    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"}'` – asp47 Jan 05 '22 at 22:09
4

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);
}
Imamudin Naseem
  • 1,604
  • 18
  • 21
  • 1
    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 Nov 30 '19 at 06:53
  • More about that [here](https://stackoverflow.com/questions/59114590/). – Lonnie Best Nov 30 '19 at 09:06
4

You cannot call JSON.stringify on Map or Set.

You will need to convert:

  • the Map into a primitive Object, using Object.fromEntries, or
  • the Set into a primitive Array, 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; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • 1
    There'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. – Stefnotch Jan 23 '23 at 15:09
3

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
  ]
}
Martin P.
  • 654
  • 8
  • 18
0

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}}'
MSOACC
  • 3,074
  • 2
  • 29
  • 50
  • This is the right answer unless I am missing something. All these other methods are making an absolute meal out of this. – MSOACC Jan 10 '23 at 14:03
  • 1
    This 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. – Stefnotch Jan 23 '23 at 15:05
-1

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 } }
Alynva
  • 499
  • 7
  • 10
  • 1
    This 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. – Stefnotch Jan 23 '23 at 15:03
-6

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.

TylerH
  • 20,799
  • 66
  • 75
  • 101
  • 1
    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](/help/how-to-answer). – Community Oct 20 '21 at 13:43
  • 2
    This sounds like a nice 10,000 foot process overview, but an actual implementation would be much more useful. – TylerH Oct 20 '21 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. – Cbaskey.it Oct 21 '21 at 03:35
  • I don't get, why this is related to the question? – Steven Shi Oct 29 '21 at 00:10