super-stringify is like JSON.stringify's superhero cousin. It converts a JavaScript object to a string, but unlike JSON.stringify it is not limited to
things that can be represented in JSON, which makes it much more useful.
Usage:
const superStringify = require('super-stringify');
console.log(superStringify(anyObject));
The superStringify function takes one input argument - a JavaScript object - plus an optional space formatter, that's treated the same way as the space formatter argument to JSON.stringify. If that object can be represented as JSON, then it produces
identical output to JSON.stringify. For example:
> let obj = { a: 1, b: 'two', c: [1, 2, 3] };
> JSON.stringify(obj)
"{"a":1,"b":"two","c":[1,2,3]}"
> superStringify(obj)
"{"a":1,"b":"two","c":[1,2,3]}"
But unlike JSON.stringify, we can super-stringify any JavaScript object:
> let obj = { a: 1 }; obj.b = obj; let c = [true, 2, () => { }]; delete c[1]; c.push(c[2]); obj.c = c;
> superStringify(obj, 2)
{
"a": 1,
"b": [ReferenceTo this],
"c": [
true,
[Empty],
[Function],
[ReferenceTo this.c[2]]
]
}
> JSON.stringify(obj, null, 2)
Error
Yes, really anything! Try in your browser for example:
> superStringify(window, 2)
JSON.stringify is kind of a JavaScript Swiss Army knife. Use it to pretty-print objects for debugging:
> console.log(JSON.stringify(obj, null, 2));
Very commonly, it's used to clone objects:
> let obj2 = JSON.parse(JSON.stringify(obj));
The problem with using it to clone objects is that it does unexpected things without telling you sometimes. For example,
suppose we have obj as follows:
> let obj = { a: { b: 1 } };
> obj.b = obj.a;
Then we clone it, and check in two ways the clone represents the same data:
> let obj2 = JSON.parse(JSON.stringify(obj));
> obj
{ a: { b: 1 }, b: { b: 1 } }
> obj2
{ a: { b: 1 }, b: { b: 1 } }
> JSON.stringify(obj) === JSON.stringify(obj2)
true
So far so good. But then surprising stuff happens:
> obj.a.b = 2; // Change something in obj
> obj2.a.b = 2; // Make the same change on obj2
> JSON.stringify(obj) === JSON.stringify(obj2)
false <-- huh?
> obj
{ a: { b: 2 }, b: { b: 2 } }
> obj2
{ a: { b: 2 }, b: { b: 1 } }
Of course, we can see why this happens, because we modified obj ourselves to include a reference to the same object twice. But when we're using objects created in other people's modules, we usually don't know
whether object properties that are themselves objects are uniquely referenced or not. And because JavaScript doesn't give us an easy way
to tell, it's very easy to introduce bugs which are nearly impossible to
find when things like this happen, and values in objects are not what you expect.
Whereas just looking at obj and obj2 at this point leaves us mystified, super-stringify explains to us exactly why what happened, happened:
> superStringify(obj)
{"a":{"b":2},"b":[ReferenceTo this.a]}
> superStringify(obj2)
{"a":{"b":2},"b":{"b":1}}
Another problem: JSON.stringify usually errors out if the input is not JSON - but not always! For example:
> let obj = [null, null]; // this object is valid JSON
> let obj2 = Array(2); // this object is not valid JSON
> JSON.stringify(obj)
[null,null] <-- OK
> JSON.stringify(obj2)
[null,null] <-- huh?
But again super-stringify actually tells you what's going on:
> superStringify(obj)
[null,null]
> superStringify(obj2)
[[Empty],[Empty]]