202

How would one JSON.stringify() a Set?

Things that did not work in Chromium 43:

var s = new Set(['foo', 'bar']);

JSON.stringify(s); // -> "{}"
JSON.stringify(s.values()); // -> "{}"
JSON.stringify(s.keys()); // -> "{}"

I would expect to get something similar to that of a serialized array.

JSON.stringify(["foo", "bar"]); // -> "["foo","bar"]"
1

4 Answers 4

219

JSON.stringify doesn't directly work with sets because the data stored in the set is not stored as properties.

But you can convert the set to an array. Then you will be able to stringify it properly.

Any of the following will do the trick:

JSON.stringify([...s]);
JSON.stringify([...s.keys()]);
JSON.stringify([...s.values()]);
JSON.stringify(Array.from(s));
JSON.stringify(Array.from(s.keys()));
JSON.stringify(Array.from(s.values()));
Sign up to request clarification or add additional context in comments.

8 Comments

Just to add some reference MDN has some examples of this and other related techniques
A list comprehension could work as well: JSON.stringify([_ for (_ of s)])
All great ways of doing it, sadly they don't work in Chromium 43 out of the box as spread and Array.from are not yet implemented. Looks like Babel to the rescue.
@MitMaro You can enable chrome://flags/#enable-javascript-harmony
A current proposal to improve the default of Set.prototype.toJSON: github.com/DavidBruant/Map-Set.prototype.toJSON
|
92

You can pass a "replacer" function to JSON.stringify:

const fooBar = {
  foo: new Set([1, 2, 3]),
  bar: new Set([4, 5, 6])
};

JSON.stringify(
  fooBar,
  (_key, value) => (value instanceof Set ? [...value] : value)
);

Result:

"{"foo":[1,2,3],"bar":[4,5,6]}"

toJSON is a legacy artifact, and a better approach is to use a custom replacer, see https://github.com/DavidBruant/Map-Set.prototype.toJSON/issues/16

7 Comments

there is shorten version in ES6 JSON.stringify(fooBar, (key, value) => value instanceof Set ? [...value] : value)
The corresponding reviver is left as an exercise for the reader? ;-)
Will you please incorporate the comment from @OzzyCzech into the answer? It's a fantastic modern solution.
I'm not sure why this is so popular, when you can't get Sets back easily from this? If you parse that result and try parsed.foo.add(1) ... it dies, because an array does't have an add function.
For anyone else coming here later wondering how to get a Set back from this, JSON.parse takes an equivalent function called reviver that's run on everything it parses. I ended up solving this by adding "__isSet" to the start of any array made out of a set and then checking for this in the reviver function, turning it back into a set when found.
|
14

While all of the above work I suggest that you subclass set and add a toJSON method to make sure that it stringify's correctly. Especially if you are going to be stringifying often. I use sets in my Redux stores and needed to make sure this was never a problem.

This is a basic implementation. Naming is just to illustrate the point pick your own style.

class JSONSet extends Set {
    toJSON () {
        return [...this]
    }
}

const set = new JSONSet([1, 2, 3])
console.log(JSON.stringify(set))

2 Comments

I wouldn't recommend this approach as toJSON is considered a legacy feature. A proposal to add toJSON to both set and map was rejected: github.com/DavidBruant/Map-Set.prototype.toJSON/issues/16
Overriding constructor is not necessary.
3

The problem with all the previous approaches is that they all convert the set into Array, which is missing the entire point of Set and indexes.

What you should do is to use an Object instead. Either convert it with the following function or simply create it as Object instead of Set.

const mySet = new Set(['hello', 'world']);
const myObj = {};
for (let value of mySet.values()) {
  myObj[value] = true;
}

Then instead of using mySet.has('hello') Do myObj.hasOwnProperty('hello').

Then stringify it as an object without a problem.

Note: The following method uses more memory because it needs to store the value as well as the key. But performence wise it's still O(1) compared to Array.includes() which is O(n) and miss the point of even using a Set.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.