JSON.stringify deep objects JSON.stringify deep objects json json

JSON.stringify deep objects


I did what I initially feared I'll have to do : I took Crockford's code and modified it for my needs. Now it builds JSON but handles

  • cycles
  • too deep objects
  • too long arrays
  • exceptions (accessors that can't legally be accessed)

In case anybody needs it, I made a GitHub repository : JSON.prune on GitHub

Here is the code :

// JSON.pruned : a function to stringify any object without overflow// example : var json = JSON.pruned({a:'e', c:[1,2,{d:{e:42, f:'deep'}}]})// two additional optional parameters ://   - the maximal depth (default : 6)//   - the maximal length of arrays (default : 50)// GitHub : https://github.com/Canop/JSON.prune// This is based on Douglas Crockford's code ( https://github.com/douglascrockford/JSON-js/blob/master/json2.js )(function () {    'use strict';    var DEFAULT_MAX_DEPTH = 6;    var DEFAULT_ARRAY_MAX_LENGTH = 50;    var seen; // Same variable used for all stringifications    Date.prototype.toPrunedJSON = Date.prototype.toJSON;    String.prototype.toPrunedJSON = String.prototype.toJSON;    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,        meta = {    // table of character substitutions            '\b': '\\b',            '\t': '\\t',            '\n': '\\n',            '\f': '\\f',            '\r': '\\r',            '"' : '\\"',            '\\': '\\\\'        };    function quote(string) {        escapable.lastIndex = 0;        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {            var c = meta[a];            return typeof c === 'string'                ? c                : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);        }) + '"' : '"' + string + '"';    }    function str(key, holder, depthDecr, arrayMaxLength) {        var i,          // The loop counter.            k,          // The member key.            v,          // The member value.            length,            partial,            value = holder[key];        if (value && typeof value === 'object' && typeof value.toPrunedJSON === 'function') {            value = value.toPrunedJSON(key);        }        switch (typeof value) {        case 'string':            return quote(value);        case 'number':            return isFinite(value) ? String(value) : 'null';        case 'boolean':        case 'null':            return String(value);        case 'object':            if (!value) {                return 'null';            }            if (depthDecr<=0 || seen.indexOf(value)!==-1) {                return '"-pruned-"';            }            seen.push(value);            partial = [];            if (Object.prototype.toString.apply(value) === '[object Array]') {                length = Math.min(value.length, arrayMaxLength);                for (i = 0; i < length; i += 1) {                    partial[i] = str(i, value, depthDecr-1, arrayMaxLength) || 'null';                }                v = partial.length === 0                    ? '[]'                    : '[' + partial.join(',') + ']';                return v;            }            for (k in value) {                if (Object.prototype.hasOwnProperty.call(value, k)) {                    try {                        v = str(k, value, depthDecr-1, arrayMaxLength);                        if (v) partial.push(quote(k) + ':' + v);                    } catch (e) {                         // this try/catch due to some "Accessing selectionEnd on an input element that cannot have a selection." on Chrome                    }                }            }            v = partial.length === 0                ? '{}'                : '{' + partial.join(',') + '}';            return v;        }    }    JSON.pruned = function (value, depthDecr, arrayMaxLength) {        seen = [];        depthDecr = depthDecr || DEFAULT_MAX_DEPTH;        arrayMaxLength = arrayMaxLength || DEFAULT_ARRAY_MAX_LENGTH;        return str('', {'': value}, depthDecr, arrayMaxLength);    };}());

An example of what can be done :

var json = JSON.pruned(window);

Note: Contrary to the code in this answer, the GitHub repository is updated when needed (documentation, compatibility, use as module in commonjs or node, specific serializations, etc.). It's a good idea to start from the repository if you need this pruning feature.


If you're using Node.js you can use util.inspect, which takes a depth argument.


I've revised @dystroy's answer, adding:

  • Indentation for sub-properties.
  • An indication of where circular references point to.
/** * Returns the JSON representation of an object. * * @param {value} object the object * @param {number} objectMaxDepth for objects, the maximum number of times to recurse into descendants * @param {number} arrayMaxLength for arrays, the maximum number of elements to enumerate * @param {string} indent the string to use for indentation * @return {string} the JSON representation */var toJSON = function(object, objectMaxDepth, arrayMaxLength, indent){    "use strict";    /**     * Escapes control characters, quote characters, backslash characters and quotes the string.     *     * @param {string} string the string to quote     * @returns {String} the quoted string     */    function quote(string)    {        escapable.lastIndex = 0;        var escaped;        if (escapable.test(string))        {            escaped = string.replace(escapable, function(a)            {                var replacement = replacements[a];                if (typeof (replacement) === "string")                    return replacement;                // Pad the unicode representation with leading zeros, up to 4 characters.                return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);            });        }        else            escaped = string;        return "\"" + escaped + "\"";    }    /**     * Returns the String representation of an object.     *      * Based on <a href="https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js">https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js</a>     *     * @param {string} path the fully-qualified path of value in the JSON object     * @param {type} value the value of the property     * @param {string} cumulativeIndent the indentation to apply at this level     * @param {number} depth the current recursion depth     * @return {String} the JSON representation of the object, or "null" for values that aren't valid     * in JSON (e.g. infinite numbers).     */    function toString(path, value, cumulativeIndent, depth)    {        switch (typeof (value))        {            case "string":                return quote(value);            case "number":                {                    // JSON numbers must be finite                    if (isFinite(value))                        return String(value);                    return "null";                }            case "boolean":                return String(value);            case "object":                {                    if (!value)                        return "null";                    var valueIndex = values.indexOf(value);                    if (valueIndex !== -1)                        return "Reference => " + paths[valueIndex];                    values.push(value);                    paths.push(path);                    if (depth > objectMaxDepth)                        return "...";                    // Make an array to hold the partial results of stringifying this object value.                    var partial = [];                    // Is the value an array?                    var i;                    if (Object.prototype.toString.apply(value) === "[object Array]")                    {                        // The value is an array. Stringify every element                        var length = Math.min(value.length, arrayMaxLength);                        // Whether a property has one or multiple values, they should be treated as the same                        // object depth. As such, we do not increment the object depth when recursing into an                        // array.                        for (i = 0; i < length; ++i)                        {                            partial[i] = toString(path + "." + i, value[i], cumulativeIndent + indent, depth,                                arrayMaxLength);                        }                        if (i < value.length)                        {                            // arrayMaxLength reached                            partial[i] = "...";                        }                        return "\n" + cumulativeIndent + "[" + partial.join(", ") + "\n" + cumulativeIndent +                            "]";                    }                    // Otherwise, iterate through all of the keys in the object.                    for (var subKey in value)                    {                        if (Object.prototype.hasOwnProperty.call(value, subKey))                        {                            var subValue;                            try                            {                                subValue = toString(path + "." + subKey, value[subKey], cumulativeIndent + indent,                                    depth + 1);                                partial.push(quote(subKey) + ": " + subValue);                            }                            catch (e)                            {                                // this try/catch due to forbidden accessors on some objects                                if (e.message)                                    subKey = e.message;                                else                                    subKey = "access denied";                            }                        }                    }                    var result = "\n" + cumulativeIndent + "{\n";                    for (i = 0; i < partial.length; ++i)                        result += cumulativeIndent + indent + partial[i] + ",\n";                    if (partial.length > 0)                    {                        // Remove trailing comma                        result = result.slice(0, result.length - 2) + "\n";                    }                    result += cumulativeIndent + "}";                    return result;                }            default:                return "null";        }    }    if (indent === undefined)        indent = "  ";    if (objectMaxDepth === undefined)        objectMaxDepth = 0;    if (arrayMaxLength === undefined)        arrayMaxLength = 50;    // Matches characters that must be escaped    var escapable =        /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;    // The replacement characters    var replacements =        {            "\b": "\\b",            "\t": "\\t",            "\n": "\\n",            "\f": "\\f",            "\r": "\\r",            "\"": "\\\"",            "\\": "\\\\"        };    // A list of all the objects that were seen (used to avoid recursion)    var values = [];    // The path of an object in the JSON object, with indexes corresponding to entries in the    // "values" variable.    var paths = [];    return toString("root", object, "", 0);};