Limit JSON stringification depth
I wanted to stringify an object on the first level without including a third-party library / too much code.
In case you look for the same, here is a quick one-liner to do so:
var json = JSON.stringify(obj, function (k, v) { return k && v && typeof v !== "number" ? (Array.isArray(v) ? "[object Array]" : "" + v) : v; });
The top level object will have no key, so it's always simply returned, however anything that is not a "number" on the next level will be casted to a string incl. a special case for arrays, otherwise those would be exposed further more.
If you don't like this special array case, please use my old solution, that I improved, too:
var json = JSON.stringify(obj, function (k, v) { return k && v && typeof v !== "number" ? "" + v : v; }); // will expose arrays as strings.
Passing an array instead of an object in the top level will work nonetheless in both solutions.
EXAMPLES:
var obj = { keyA: "test", keyB: undefined, keyC: 42, keyD: [12, "test123", undefined]}obj.keyD.push(obj);obj.keyE = obj;var arr = [12, "test123", undefined];arr.push(arr);var f = function (k, v) { return k && v && typeof v !== "number" ? (Array.isArray(v) ? "[object Array]" : "" + v) : v; };var f2 = function (k, v) { return k && v && typeof v !== "number" ? "" + v : v; };console.log("object:", JSON.stringify(obj, f));console.log("array:", JSON.stringify(arr, f));console.log("");console.log("with array string cast, so the array gets exposed:");console.log("object:", JSON.stringify(obj, f2));console.log("array:", JSON.stringify(arr, f2));
Here is a function that respects the built-in JSON.stringify()
rules while also limiting depth:
function stringify(val, depth, replacer, space) { depth = isNaN(+depth) ? 1 : depth; function _build(key, val, depth, o, a) { // (JSON.stringify() has it's own rules, which we respect here by using it for property iteration) return !val || typeof val != 'object' ? val : (a=Array.isArray(val), JSON.stringify(val, function(k,v){ if (a || depth > 0) { if (replacer) v=replacer(k,v); if (!k) return (a=Array.isArray(v),val=v); !o && (o=a?[]:{}); o[k] = _build(k, v, a?depth:depth-1); } }), o||(a?[]:{})); } return JSON.stringify(_build('', val, depth), null, space);}
How it works:
_build()
is called recursively to build the nested objects and arrays to the requested depth.JSON.stringify()
is used to iterate over each object's immediate properties to respect the built-in rules. 'undefined' is always returned from the internal replacer so no JSON is actually constructed yet. Keep in mind, the first time the internal replacer is called the key is empty (which is the item to be stringified).JSON.stringify()
is called on the final result to produce the actual JSON.
Example:
var value={a:[12,2,{y:3,z:{q:1}}],s:'!',o:{x:1,o2:{y:1}}};console.log(stringify(value, 0, null, 2));console.log(stringify(value, 1, null, 2));console.log(stringify(value, 2, null, 2));{}{ "a": [ 12, 2, {} ], "s": "!", "o": {}}{ "a": [ 12, 2, { "y": 3, "z": {} } ], "s": "!", "o": { "x": 1, "o2": {} }}
(for a version that handles cyclical references, see here: https://stackoverflow.com/a/57193345/1236397 - includes a TypeScript version)
Update: Fixed a bug where empty arrays rendered as empty objects.
Make a deep clone of your object (with a library such as low-dash), do what ever pruning you want to do and then pass it to JSON.stringify. I would not try to re-invent JSON.stringify, that is effort in the wrong place.
[EDIT] looks like someone already did what you were suggesting: JSON.stringify deep objects
I wouldn't recommend this though because the native JSON.stringify is always going to be faster and more robust
[EDIT] here is a library that seems to do what you want: http://philogb.github.io/jit/static/v20/Docs/files/Core/Core-js.html#$jit.json.prune