JSON.stringify,避免TypeError:将循环结构转换为JSON

Har*_*rry 608 javascript json node.js

我有一个大对象,我想转换为JSON并发送.但它具有圆形结构.我想抛出任何存在的循环引用并发送任何可以进行字符串化的内容.我怎么做?

谢谢.

var obj = {
  a: "foo",
  b: obj
}
Run Code Online (Sandbox Code Playgroud)

我想将obj字符串化为:

{"a":"foo"}
Run Code Online (Sandbox Code Playgroud)

Ere*_*evi 627

在Node.js中,您可以使用util.inspect(object).它会自动用"[Circular]"替换循环链接.


虽然是内置的(无需安装),但您必须导入它

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
Run Code Online (Sandbox Code Playgroud) 要使用它,只需致电
console.log(util.inspect(myObject))
Run Code Online (Sandbox Code Playgroud)

另请注意,您可以传递options对象进行检查(参见上面的链接)

inspect(myObject[, options: {showHidden, depth, colors, showProxy, ...moreOptions}])
Run Code Online (Sandbox Code Playgroud)



请阅读并向下面的评论者致敬...

  • util是一个内置模块,您不必安装它. (128认同)
  • @Mitar它是内置的,但你仍然需要加载模块`var util = require('util');` (17认同)
  • 不要像我这样的笨蛋,它只是*`obj_str = util.inspect(thing)`,NOT <s>`garbage_str = JSON.stringify(util.inspect(thing))`</ s> (12认同)
  • 的console.log(util.inspect(OBJ)) (8认同)
  • 这比检查类型更好.为什么不能像这样工作呢?如果它知道有一个循环引用,为什么它不能被告知忽略它? (5认同)
  • 这是一个很好的建议!非常感谢!我从没想过看到一个控制台记录的对象会如此令人满意,但就好像你打破了我现在打开的所有objs一样. (2认同)
  • 请注意,如果由于某种原因需要使用JSON.stringify,则不能使用util.inspect,因为它输出的是字符串,而不是对象. (2认同)
  • 请注意,util.inspect并不总是返回有效的JSON.例如`util.inspect(new Error('这是一个示例错误'))` (2认同)
  • 只是想指出util.insect()不会产生有效的Json字符串...所以它与JSON.stringify()有点不同 (2认同)

Rob*_*b W 551

JSON.stringify与自定义替换器一起使用.例如:

// Demo: Circular reference
var circ = {};
circ.circ = circ;

// Note: cache should not be re-used by repeated calls to JSON.stringify.
var cache = [];
JSON.stringify(circ, function(key, value) {
    if (typeof value === 'object' && value !== null) {
        if (cache.indexOf(value) !== -1) {
            // Duplicate reference found, discard key
            return;
        }
        // Store value in our collection
        cache.push(value);
    }
    return value;
});
cache = null; // Enable garbage collection
Run Code Online (Sandbox Code Playgroud)

此示例中的替换程序不是100%正确(取决于您对"重复"的定义).在以下情况中,将丢弃一个值:

var a = {b:1}
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o, ...);
Run Code Online (Sandbox Code Playgroud)

但概念是:使用自定义替换器,并跟踪解析的对象值.

  • 这是错误的,因为它将跳过包含两次的对象的第二次出现,即使不是真正的循环结构.`var a = {id:1}; JSON.stringify([A,A]);` (4认同)
  • @ user2451227"此示例中的替换程序不是100%正确(取决于您对"复制"的定义).但概念代表:使用自定义替换程序,并跟踪已解析的对象值." (3认同)
  • 这里的GC问题可以说是多余的.如果它作为单个脚本运行,则脚本会立即终止.如果将其封装在一个实现函数中,那么`cache`将无法访问https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management (3认同)
  • @ruffin:自从发表该评论以来,我从 Isaac 获取了 JSON Stringify Safe 库并重写了它:https://github.com/isaacs/json-stringify-safe。我不确定所谓的实时鲁棒 Crockford 代码是做什么的。它看起来过于复杂,并且似乎做了我上面警告的同样糟糕的线性检查。 (2认同)

Art*_*sun 114

我想知道为什么没有人从MDN页面发布正确的解决方案 ......

const getCircularReplacer = () => {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

JSON.stringify(circularReference, getCircularReplacer());
Run Code Online (Sandbox Code Playgroud)

看到的值应存储在一个集合中,而不是存储数组中(每个元素都会调用replacer ),并且不需要尝试链中的JSON.stringify 每个元素,从而导致循环引用.

与接受的答案一样,此解决方案会删除所有重复值,而不仅仅是循环.但至少它没有指数复杂性.

  • 尤达说:“如果仍然支持IE,那么应该使用一个翻译器。” (33认同)
  • ```replacer = () =&gt; { const saw = new WeakSet(); return (key, value) =&gt; { if (typeof value === "object" &amp;&amp; value !== null) { if (seen.has(value)) { return; } } 看到.add(值); 返回值;}; } () =&gt; { const saw = new WeakSet(); return (key, value) =&gt; { if (typeof value === "object" &amp;&amp; value !== null) { if (seen.has(value)) { return; } } 看到.add(值); … JSON.stringify({a:1, b: '2'}, Replacer)``` 在 chrome 中返回 `undefined` (3认同)
  • 这个答案(就像[接受的答案](/sf/ask/813164131/#answer-11616993) )有一个错误([此处]评论(/sf/ask/813164131/#comment37945084_11616993)通过 [user2451227](/sf/users/171585921/)),当`o = {}; JSON.stringify([o, o], getCircularReplacer())`。 (3认同)
  • 这会检测结构中的重复对象,而不是圆形对象。虽然循环将包含重复引用,但并非所有重复项都是循环对象。因此,这也删除了非循环引用。例如,`const a = {greeting: "hi"}; const obj = {first: a, secondary: a};`,在字符串化 `obj` 时使用 `circular()` 将删除 `second` 属性,即使它根本不是循环的。因此,它删除了不必要的东西。 (2认同)

小智 66

做就是了

npm i --save circular-json
Run Code Online (Sandbox Code Playgroud)

然后在你的js文件中

const JSON = require('circular-json');
...
const json = JSON.stringify(obj);
Run Code Online (Sandbox Code Playgroud)

你也可以这样做

const CircularJSON = require('circular-json');
Run Code Online (Sandbox Code Playgroud)

https://github.com/WebReflection/circular-json

注意:我与此软件包无关.但我确实用它.

  • 我认为使用模块可能需要更多的理由,而不是"只做".并且原则上覆盖`JSON`并不好. (13认同)
  • 请注意,“扁平化”(和循环 json?)包不会复制 JSON.stringify() 功能。它创建自己的非 JSON 格式。(例如,`Flatted.stringify({blah: 1})` 的结果是 `[{"blah":1}]`)我看到有人试图就此提出问题,作者斥责他们并将问题锁定为注释。 (9认同)

guy*_*abi 47

我真的很喜欢Trindaz的解决方案 - 更详细,但它有一些错误.我为那些喜欢它的人修好了它们.

另外,我在缓存对象上添加了长度限制.

如果我打印的对象非常大 - 我的意思是无限大 - 我想限制我的算法.

JSON.stringifyOnce = function(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        if ( printedObjects.length > 2000){ // browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects
        return 'object too long';
        }
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if ( key == ''){ //root element
             printedObjects.push(obj);
            printedObjectKeys.push("root");
             return value;
        }

        else if(printedObjIndex+"" != "false" && typeof(value)=="object"){
            if ( printedObjectKeys[printedObjIndex] == "root"){
                return "(pointer to root)";
            }else{
                return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase()  : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
            }
        }else{

            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
};
Run Code Online (Sandbox Code Playgroud)

  • @Isak我编辑了代码以包含空检查. (2认同)
  • //浏览器的打印量不会超过20K - 但是你将限制设置为2k.或许改变未来? (2认同)

Ale*_*lls 37

@ RobW的回答是正确的,但这更高效!因为它使用hashmap/set:

const customStringify = function (v) {
  const cache = new Set();
  return JSON.stringify(v, function (key, value) {
    if (typeof value === 'object' && value !== null) {
      if (cache.has(value)) {
        // Circular reference found
        try {
          // If this value does not reference a parent it can be deduped
         return JSON.parse(JSON.stringify(value));
        }
        catch (err) {
          // discard key if value cannot be deduped
         return;
        }
      }
      // Store value in our set
      cache.add(value);
    }
    return value;
  });
};
Run Code Online (Sandbox Code Playgroud)


Nux*_*Nux 36

请注意,JSON.decycleDouglas Crockford 还实施了一种方法.看他的 cycle.js.这允许您对几乎任何标准结构进行字符串化:

var a = [];
a[0] = a;
a[1] = 123;
console.log(JSON.stringify(JSON.decycle(a)));
// result: '[{"$ref":"$"},123]'.
Run Code Online (Sandbox Code Playgroud)

您还可以使用retrocycle方法重新创建原始对象.因此,您不必从对象中删除循环以对其进行字符串化.

然而,这不适用于DOM节点(这是现实生活中使用情况下循环的典型原因).例如,这将抛出:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a)));
Run Code Online (Sandbox Code Playgroud)

我已经做了一个分叉来解决这个问题(参见我的cycle.js fork).这应该工作正常:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a, true)));
Run Code Online (Sandbox Code Playgroud)

请注意,在我的fork中JSON.decycle(variable)工作原样并且在variable包含DOM节点/元素时会抛出异常.

当你使用时,JSON.decycle(variable, true)你接受结果不可逆的事实(retrocycle不会重新创建DOM节点).但是DOM元素在某种程度上应该是可识别的.例如,如果div元素具有id,则它将被替换为字符串"div#id-of-the-element".

  • 使用它们时,他和您的代码都给我一个“ RangeError:超出最大调用堆栈大小”。 (2认同)

mik*_*eil 22

我建议从@isaacs中查看json-stringify- safe--它在NPM中使用.

顺便说一句 - 如果您没有使用Node.js,您只需从源代码相关部分复制并粘贴第4-27行.

安装:

$ npm install json-stringify-safe --save
Run Code Online (Sandbox Code Playgroud)

使用:

// Require the thing
var stringify = require('json-stringify-safe');

// Take some nasty circular object
var theBigNasty = {
  a: "foo",
  b: theBigNasty
};

// Then clean it up a little bit
var sanitized = JSON.parse(stringify(theBigNasty));
Run Code Online (Sandbox Code Playgroud)

这会产生:

{
  a: 'foo',
  b: '[Circular]'
}
Run Code Online (Sandbox Code Playgroud)

请注意,就像提到的@Rob W的vanilla JSON.stringify函数一样,您也可以通过传入"replacer"函数作为第二个参数来自定义清理行为stringify().如果你发现自己需要如何做一个简单的例子,我只是写了要挟的错误,正则表达式和功能为人类可读的字符串自定义替代品在这里.


Tri*_*daz 12

对于未来的googlers在您知道所有循环引用的键时搜索此问题的解决方案,您可以使用JSON.stringify函数周围的包装来排除循环引用.请参阅https://gist.github.com/4653128上的示例脚本.

该解决方案基本上归结为保持对数组中先前打印的对象的引用,并在返回值之前在replacer函数中检查该对象.它比仅排除循环引用更加紧缩,因为它还排除了两次打印对象,其中一个副作用是避免循环引用.

包装示例:

function stringifyOnce(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if(printedObjIndex && typeof(value)=="object"){
            return "(see " + value.constructor.name.toLowerCase() + " with key " + printedObjectKeys[printedObjIndex] + ")";
        }else{
            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
}
Run Code Online (Sandbox Code Playgroud)

  • 好的代码.你有一个愚蠢的错误,你写`if(printedObjIndex)`你应该写'if(printedObjIndex == false)`因为`index`也可以是'0`,它被翻译成'false`,除非你明确说明否则. (3认同)

esh*_*lev 5

var a={b:"b"};
a.a=a;
JSON.stringify(preventCircularJson(a));
Run Code Online (Sandbox Code Playgroud)

评估为:

"{"b":"b","a":"CIRCULAR_REFERENCE_REMOVED"}"
Run Code Online (Sandbox Code Playgroud)

具有以下功能:

/**
 * Traverses a javascript object, and deletes all circular values
 * @param source object to remove circular references from
 * @param censoredMessage optional: what to put instead of censored values
 * @param censorTheseItems should be kept null, used in recursion
 * @returns {undefined}
 */
function preventCircularJson(source, censoredMessage, censorTheseItems) {
    //init recursive value if this is the first call
    censorTheseItems = censorTheseItems || [source];
    //default if none is specified
    censoredMessage = censoredMessage || "CIRCULAR_REFERENCE_REMOVED";
    //values that have allready apeared will be placed here:
    var recursiveItems = {};
    //initaite a censored clone to return back
    var ret = {};
    //traverse the object:
    for (var key in source) {
        var value = source[key]
        if (typeof value == "object") {
            //re-examine all complex children again later:
            recursiveItems[key] = value;
        } else {
            //simple values copied as is
            ret[key] = value;
        }
    }
    //create list of values to censor:
    var censorChildItems = [];
    for (var key in recursiveItems) {
        var value = source[key];
        //all complex child objects should not apear again in children:
        censorChildItems.push(value);
    }
    //censor all circular values
    for (var key in recursiveItems) {
        var value = source[key];
        var censored = false;
        censorTheseItems.forEach(function (item) {
            if (item === value) {
                censored = true;
            }
        });
        if (censored) {
            //change circular values to this
            value = censoredMessage;
        } else {
            //recursion:
            value = preventCircularJson(value, censoredMessage, censorChildItems.concat(censorTheseItems));
        }
        ret[key] = value

    }

    return ret;
}
Run Code Online (Sandbox Code Playgroud)