序列化包含循环对象值的对象


151

我有一个对象(解析树),其中包含子节点,这些子节点是对其他节点的引用。

我想使用序列化此对象JSON.stringify(),但是我得到了

TypeError:循环对象值

因为我提到的结构。

我该如何解决?对我而言,在序列化对象中是否表示对其他节点的引用并不重要。

另一方面,在创建对象时从对象中删除这些属性似乎很繁琐,并且我不想更改解析器(水仙)。


1
没有一些代码,我们将无法为您提供帮助。请发布对象和/或JSON输出的相关位以及用于序列化的JS。
Bojangles 2012年

1
您可以向内部引用的那些属性添加一些前缀吗?
wheresrhys 2012年

@Loic cycle.js在这里让道格拉斯·克罗克福德(Douglas Crockford)回答是很有价值的,因为它是很多情况下最合适的解决方案。您似乎应该发布该答案,因为您是第一个引用它的人(在下面的评论中)。如果您不想自己将其发布为答案,我最终会这样做。
杰里米·班克斯


1
我希望JSON更智能,或者更简单的解决方法。对于简单的(!)调试目的,解决方案太麻烦了。
BluE

Answers:


220

使用的第二个参数stringify,该替代品的功能,以排除已序列化对象:

var seen = [];

JSON.stringify(obj, function(key, val) {
   if (val != null && typeof val == "object") {
        if (seen.indexOf(val) >= 0) {
            return;
        }
        seen.push(val);
    }
    return val;
});

http://jsfiddle.net/mH6cJ/38/

正如在其他注释中正确指出的那样,此代码删除了每个“可见”对象,而不仅仅是“递归”对象。

例如,用于:

a = {x:1};
obj = [a, a];

结果将不正确。如果您的结构是这样的,则可能要使用Crockford的decycle或此(更简单的)函数,该函数仅将递归引用替换为null:

function decycle(obj, stack = []) {
    if (!obj || typeof obj !== 'object')
        return obj;
    
    if (stack.includes(obj))
        return null;

    let s = stack.concat([obj]);

    return Array.isArray(obj)
        ? obj.map(x => decycle(x, s))
        : Object.fromEntries(
            Object.entries(obj)
                .map(([k, v]) => [k, decycle(v, s)]));
}

//

let a = {b: [1, 2, 3]}
a.b.push(a);

console.log(JSON.stringify(decycle(a)))


3
好啊!谢谢,我要试试这个。我找到了由道格拉斯· 克罗克福德(Douglas Crockford )创建的解决方案(github.com/douglascrockford/JSON-js/blob/master/cycle.js),但是由于我不确定它附带的许可,您描述的简单解决方案将是完美的!
Loic Duros

3
@LoicDuros许可证是“公共领域”。意思是,您可以用它做任何您想做的事情。
Ates Goral 2012年

1
此代码会产生循环循环,请小心使用,极有可能导致应用崩溃。需要正确的分号,并且不能在事件对象上使用!
Ol Sen

3
这不仅删除了循环引用,还删除了多次出现的内容。除非已经序列化的对象是新对象的“父”对象,否则您不应删除它
Gio 2014年

1
好答案!我对此做了一些修改,将该函数更改为递归函数,以便子对象可以像克隆父对象一样被克隆。
HoldOffHunger '18

2

我创建了一个GitHub Gist,它能够检测循环结构并对其进行解编码并进行编码:https : //gist.github.com/Hoff97/9842228

要进行转换,只需使用JSONE.stringify / JSONE.parse。它还对功能进行解编码。如果要禁用此功能,只需删除第32-48和61-85行即可。

var strg = JSONE.stringify(cyclicObject);
var cycObject = JSONE.parse(strg);

您可以在此处找到一个小提琴示例:

http://jsfiddle.net/hoff97/7UYd4/


2

这是一种替代答案,但是由于很多人来这里调试它们的圆形对象,而且如果不引入大量代码的话,实际上并没有什么好办法。

不那么知名的一项功能JSON.stringify()console.table()。只需调用console.table(whatever);,它将以表格格式将变量记录在控制台中,从而使读取变量的内容变得相当容易和方便。


1

更省钱,它显示了循环对象的位置。

<script>
var jsonify=function(o){
    var seen=[];
    var jso=JSON.stringify(o, function(k,v){
        if (typeof v =='object') {
            if ( !seen.indexOf(v) ) { return '__cycle__'; }
            seen.push(v);
        } return v;
    });
    return jso;
};
var obj={
    g:{
        d:[2,5],
        j:2
    },
    e:10
};
obj.someloopshere = [
    obj.g,
    obj,
    { a: [ obj.e, obj ] }
];
console.log('jsonify=',jsonify(obj));
</script>

产生

jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}

但仍有与此代码,如果有人将建立与对象的问题obj.b=this',如果有人知道如何防止做出了一个错误定范围的很长Calcs(计算)this会很高兴地看到这里
01森

2
这应该是seen.indexOf(v) != -1

1

我也创建了一个github项目,该项目可以序列化循环对象并还原类(如果将其保存在诸如String的serializename属性中)

var d={}
var a = {b:25,c:6,enfant:d};
d.papa=a;
var b = serializeObjet(a);
assert.equal(  b, "{0:{b:25,c:6,enfant:'tab[1]'},1:{papa:'tab[0]'}}" );
var retCaseDep = parseChaine(b)
assert.equal(  retCaseDep.b, 25 );
assert.equal(  retCaseDep.enfant.papa, retCaseDep );

https://github.com/bormat/serializeStringifyParseCyclicObject

编辑:我已经将我的脚本转换为NPM https://github.com/bormat/borto_circular_serialize,并且我将函数名称从法语更改为英语。


这个例子不符合要点。要点有错误。
恩斯特恩斯特

不错的主意-但是一旦准备就绪:-)如果您将其发布在npm中,也许您甚至会为此开发类型,那么它可能会变得非常流行。
彼得-恢复莫妮卡

1

这是带有循环引用的数据结构的示例: 工具仓库

function makeToolshed(){
    var nut = {name: 'nut'}, bolt = {name: 'bolt'};
    nut.needs = bolt; bolt.needs = nut;
    return { nut: nut, bolt: bolt };
}

当你想KEEP循环引用(当你反序列化恢复它们,而不是“的摧毁”),你有2个选择,我会在这里进行比较。首先是Douglas Crockford的cycle.js,其次是我的西伯利亚软件包。两者都通过首先“回收”对象来工作,即构造“包含相同信息”的另一个对象(没有任何循环引用)。

克罗克福德先生先行:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

如您所见,JSON的嵌套结构得以保留,但是有一个新东西,即具有特殊$ref属性的对象。让我们看看它是如何工作的。

root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]

美元符号代表根。.bolt$ref告诉我们,.bolt是“已经看到”对象,特殊属性的值(在这里,字符串$ [“螺母”] [“需求”])告诉我们哪里,最先看到的===上面。第二$ref和第二===以上。

让我们使用一个合适的深度平等测试(即,deepGraphEqual从对这个问题的公认答案中的Anders Kaseorg 函数)来看克隆是否有效。

root = makeToolshed();
clone = JSON.retrocycle(JSON.decycle(root));
deepGraphEqual(root, clone) // true
serialized = JSON.stringify(JSON.decycle(root));
clone2 = JSON.retrocycle(JSON.parse(serialized));
deepGraphEqual(root, clone2); // true

现在,西伯利亚:

JSON.Siberia.forestify(makeToolshed())

JSON_Siberia_forestify_makeToolshed

西伯利亚不尝试模仿“经典” JSON,没有嵌套结构。对象图以“平面”方式描述。对象图的每个节点都变成一棵扁平树(带有纯整数值的纯键值对列表),它是.forest.索引0处的根对象,索引较高的处则找到的其他节点。对象图和负值(林中某棵树的某些键的负值)指向该atoms数组(该数组是通过types数组键入的,但此处将跳过键入细节)。所有终端节点都在atoms表中,所有非终端节点都在forest表中,您可以立即看到对象图有多少个节点,即forest.length。让我们测试一下是否可行:

root = makeToolshed();
clone = JSON.Siberia.unforestify(JSON.Siberia.forestify(root));
deepGraphEqual(root, clone); // true
serialized = JSON.Siberia.stringify(JSON.Siberia.forestify(root));
clone2 = JSON.Siberia.unforestify(JSON.Siberia.unstringify(serialized));
deepGraphEqual(root, clone2); // true

比较

稍后会添加部分。


0
function stringifyObject ( obj ) {
  if ( _.isArray( obj ) || !_.isObject( obj ) ) {
    return obj.toString()
  }
  var seen = [];
  return JSON.stringify(
    obj,
    function( key, val ) {
      if (val != null && typeof val == "object") {
        if ( seen.indexOf( val ) >= 0 )
          return
          seen.push( val )
          }
      return val
    }
  );
}

缺少先决条件,否则数组对象中的整数值将被截断,即[[08.11.2014 12:30:13,1095]] 1095减少为095。


得到RefrenceError:找不到变量:_
amit pandya

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.