Javascript JSON.stringify 深层对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13861254/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 14:58:50  来源:igfitidea点击:

JSON.stringify deep objects

javascriptjsonstringify

提问by Denys Séguret

I need a function building a JSON valid string from any argument but :

我需要一个函数从任何参数构建一个 JSON 有效字符串,但是:

  • avoiding recursivity problem by not adding objects twice
  • avoiding call stack size problem by truncating past a given depth
  • 通过不添加对象两次来避免递归问题
  • 通过截断超过给定深度来避免调用堆栈大小问题

Generally it should be able to process big objects, at the cost of truncating them.

通常它应该能够处理大对象,代价是截断它们。

As reference, this code fails :

作为参考,此代码失败:

var json = JSON.stringify(window);

Avoiding recursivity problem is simple enough :

避免递归问题很简单:

var seen = [];
return JSON.stringify(o, function(_, value) {
    if (typeof value === 'object' && value !== null) {
        if (seen.indexOf(value) !== -1) return;
        else seen.push(value);
    }
    return value;
});

But for now, apart copying and changing Douglas Crockford's codeto keep track of the depth, I didn't find any way to avoid stack overflow on very deep objects like windowor any event. Is there a simple solution ?

但现在,除了复制并更改道格拉斯Crockford的代码追踪深度的,我没有发现任何方式,以避免像很深刻的对象堆栈溢出window或任何event。有简单的解决方案吗?

回答by Denys Séguret

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

我做了我最初担心我必须做的事情:我采用了 Crockford 的代码并根据我的需要对其进行了修改。现在它构建 JSON 但处理

  • 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

如果有人需要它,我创建了一个 GitHub 存储库:GitHub 上的 JSON.prune

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 repositoryis 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.

注意:与此答案中的代码相反,GitHub 存储库在需要时更新(文档、兼容性、在 commonjs 或节点中用作模块、特定序列化等)。如果您需要此修剪功能,最好从存储库开始。

回答by Rhys van der Waerden

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

如果您使用的是 Node.js,则可以使用util.inspect,它带有一个深度参数。

回答by Gili

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

我已经修改了@dystroy 的回答,并补充说:

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

回答by Gábor Lipták

You can simply use a Censorfunction like in the example below:

您可以简单地使用如下示例中的Censor函数:

function censor(key, value) {
  if (typeof(value) == "string") {
    return undefined;
  }
  return value;
}

var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
var jsonString = JSON.stringify(foo, censor);

The output is {"week":45,"month":7}.

输出是{"week":45,"month":7}

So as for your example, you have to return undefined if you have a value object, which is a window.

因此,对于您的示例,如果您有一个值对象(即窗口),则必须返回 undefined。

回答by James Wilkins

Here is a function that respects the built-in JSON.stringify() rules while also limiting depth. This version handles cyclical references by making them either null, or using an optional callback to get an object ID (such as a GUID).

这是一个尊重内置 JSON.stringify() 规则同时限制深度的函数。此版本通过将循环引用设为 null 或使用可选回调来获取对象 ID(例如 GUID)来处理循环引用。

function stringify(val, depth, replacer, space, onGetObjID) {
    depth = isNaN(+depth) ? 1 : depth;
    var recursMap = new WeakMap();
    function _build(val, depth, o, a, r) { // (JSON.stringify() has it's own rules, which we respect here by using it for property iteration)
        return !val || typeof val != 'object' ? val
            : (r = recursMap.has(val), recursMap.set(val,true), a = Array.isArray(val),
               r ? (o=onGetObjID&&onGetObjID(val)||null) : 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(v, a?depth:depth-1); } }),
               o===void 0 ? (a?[]:{}) : o);
    }
    return JSON.stringify(_build(val, depth), null, space);
}

var o = {id:'SOMEGUID',t:true};
var value={a:[12,2,{y:3,z:{o1:o}}],s:'!',b:{x:1,o2:o,o3:o}};

console.log(stringify(value, 0, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2));
console.log(stringify(value, 1, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2));
console.log(stringify(value, 2, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2));
console.log(stringify(value, 3, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2));
console.log(stringify(value, 4, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2, (v)=>{return v.id}));

{}

{
  "a": [
    12,
    2,
    {}
  ],
  "s": "!",
  "b": {}
}

{
  "a": [
    12,
    2,
    {
      "y": 3,
      "z": {}
    }
  ],
  "s": "!",
  "b": {
    "x": 1,
    "o2": {},
    "o3": null
  }
}

{
  "a": [
    12,
    2,
    {
      "y": 3,
      "z": {
        "o1": {}
      }
    }
  ],
  "s": "!",
  "b": {
    "x": 1,
    "o2": null,
    "o3": null
  }
}

{
  "a": [
    12,
    2,
    {
      "y": 3,
      "z": {
        "o1": {
          "id": "SOMEGUID",
          "t": true
        }
      }
    }
  ],
  "s": "!",
  "b": {
    "x": 1,
    "o2": "SOMEGUID",
    "o3": "SOMEGUID"
  }

(taken from my post here https://stackoverflow.com/a/57193068/1236397)

(取自我在这里的帖子https://stackoverflow.com/a/57193068/1236397

Here is a TypeScript version:

这是一个打字稿版本:

/** A more powerful version of the built-in JSON.stringify() function that uses the same function to respect the
 * built-in rules while also limiting depth and supporting cyclical references.
 */
export function stringify(val: any, depth: number, replacer: (this: any, key: string, value: any) => any, space?: string | number, onGetObjID?: (val: object) => string): string {
    depth = isNaN(+depth) ? 1 : depth;
    var recursMap = new WeakMap();
    function _build(val: any, depth: number, o?: any, a?: boolean, r?: boolean) {
        return !val || typeof val != 'object' ? val
            : (r = recursMap.has(val),
                recursMap.set(val, true),
                a = Array.isArray(val),
                r ? (o = onGetObjID && onGetObjID(val) || null) : 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(v, a ? depth : depth - 1); } }),
                o === void 0 ? (a?[]:{}) : o);
    }
    return JSON.stringify(_build(val, depth), null, space);
}

Note:Arrays are treated like strings - an array of primitive values; thus, any nested object items are treated as the next level instead of the array object itself (much like how a string can be an array of characters, but is one entity).

注意:数组被视为字符串 - 原始值数组;因此,任何嵌套的对象项都被视为下一级,而不是数组对象本身(很像字符串可以是字符数组,但它是一个实体)。

Update: Fixed a bug where empty arrays rendered as empty objects.

更新:修复了空数组呈现为空对象的错误。

回答by nfroidure

I think that the format you're using is just unproper to do what you want. Getting all datas contained in the window object to a single JSON string suppose you keep this string in memory during you're building her causing issues you encountered.

我认为您使用的格式不适合做您想做的事。将 window 对象中包含的所有数据获取到单个 JSON 字符串,假设您在构建窗口对象期间将此字符串保存在内存中,从而导致您遇到的问题。

You need a format givin you the ability to send datas as it is parsed from the window object in order to free memory on the fly. For that matter, you should use something like CSV, Text or VarStream ( https://github.com/nfroidure/VarStream).

您需要一种格式,使您能够在从 window 对象解析数据时发送数据,以便即时释放内存。就此而言,您应该使用 CSV、Text 或 VarStream ( https://github.com/nfroidure/VarStream) 之类的内容。

You could also iterate throught object and try to JSON.stringify them in a try ... catch. If the try is a success, you send the JSON file, if it fails, you iterate througt the object properties with the same try ... catch etc... But it's a ugly workaround i do not encourage you to use.

您还可以遍历对象并尝试在 try ... catch 中对它们进行 JSON.stringify。如果尝试成功,则发送 JSON 文件,如果失败,则使用相同的 try ... catch 等遍历对象属性...但这是一个丑陋的解决方法,我不鼓励您使用。

回答by iki

Here's my stringifier to stripped JSON for safe logging of objects with cyclical references, DOM elements, angular scopes, or window.

这是我的字符串化器,用于剥离 JSON,以安全记录具有循环引用、DOM 元素、角度范围或窗口的对象。

Prevents TypeError: Converting circular structure to JSONby replacing circular references with ''.

TypeError: Converting circular structure to JSON通过用''替换循环引用来防止。

Prevents RangeError: Maximum call stack size exceeded. However, it's recommended to use maxDepth or filterObjects anyway, because serializing very deep objects costs both time and space, which may lower its usability for general logging, and even make the test browser disconnect when used in tests.

防止RangeError: Maximum call stack size exceeded. 不过还是推荐使用 maxDepth 或者 filterObjects ,因为序列化很深的对象既消耗时间又消耗空间,这可能会降低它对于一般日志的可用性,甚至在测试中使用时会导致测试浏览器断开连接。

Optionally:

可选:

  • limits object inspection depth (not implemented yet),
  • filters objects (like window, test framework, test runner),
  • filters DOM elements,
  • filters angular object $attributes.
  • 限制对象检查深度(尚未实现),
  • 过滤器对象(如窗口、测试框架、测试运行器),
  • 过滤DOM元素,
  • 过滤角对象 $attributes。

Source+comments: https://gist.github.com/iki/9371373

来源+评论:https: //gist.github.com/iki/9371373

回答by Jarle

(function (input, level) {
    if (!input)
        return input;

    level = level || 4;

    var objectsAlreadySerialized = [input],
        objDepth = [input];

    return JSON.stringify(input, function (key, value) {
        if (key) {
            if (typeof value === 'object') {
                if (objectsAlreadySerialized.indexOf(value) !== -1)
                    return undefined;

                objectsAlreadySerialized.push(value);
            }

            if (objDepth.indexOf(this) === -1)
                objDepth.push(this);
            else while(objDepth[objDepth.length-1] !== this)
                objDepth.pop();

            if (objDepth.length > level)
                return undefined;
        }

        return value;
    });
})(window, 6)

回答by Nick

You could just maintain the depth you're at:

你可以保持你所处的深度:

function stringify(obj, currentDepth, maxDepth) {
  if (currentDepth == maxDepth) return '[Warning: max level reached]'
  var str = '{';
  for (var key in obj) {
    str += key + ': ' + typeof obj == 'object' ?
        stringify(obj[key], currentDepth + 1, maxDepth) :
        obj[key];
  }
  return str + '}'
}

(just example- obviously this snippet doesn't detect recursion)

(只是示例-显然此代码段未检测到递归)