172

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

我想序列化这个对象,使用JSON.stringify(),但我得到

TypeError:循环对象值

因为我提到的构造。

我该如何解决这个问题?这些对其他节点的引用是否在序列化对象中表示对我来说并不重要。

另一方面,在创建对象时从对象中删除这些属性似乎很乏味,我不想对解析器(narcissus)进行更改。

4

8 回答 8

250

使用替换函数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或这个(更简单的)函数,它只是用空值替换递归引用:

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)))

于 2012-02-21T17:41:16.827 回答
9

这是一种替代答案,但是由于很多人会来这里是为了调试他们的圆形对象,并且没有真正的好方法可以做到这一点,而无需拉入一堆代码,就这样吧。

JSON.stringify()一个不像console.table(). 只需调用console.table(whatever);,它将以表格格式将变量记录在控制台中,使得阅读变量的内容变得相当容易和方便。

于 2020-07-10T17:26:01.800 回答
5

下面是一个带有循环引用的数据结构示例: 工具棚CY

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

当您希望保留循环引用(在反序列化时恢复它们,而不是“核对”它们),您有 2 个选择,我将在此处进行比较。首先是 Douglas Crockford 的cycle.js,其次是我的siberia包。两者都首先“回收”对象,即构造另一个“包含相同信息”的对象(没有任何循环引用)。

克罗克福德先生先说:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

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

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

美元符号代表根。.bolthave$ref告诉我们这.bolt是一个“已经见过”的对象,并且那个特殊属性的值(这里是字符串 $["nut"]["needs"])告诉我们在哪里,见===上文。上面的第二个$ref和第二个也是===如此。

让我们使用一个合适的深度相等测试(即 Anders Kaseorg's deepGraphEqualfunction from accepted answer to this question)来看看克隆是否有效。

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

Siberia 不会尝试模仿“经典”JSON,没有嵌套结构。对象图以“平面”方式描述。对象图的每个节点都变成了一个扁平树(纯键值对列表,只有整数值),它是一个条目.forest.在索引为零时,我们找到根对象,在更高的索引处,我们找到其他节点对象图和负值(森林中某棵树的某个键)指向atoms数组,(通过 types 数组输入,但我们将在此处跳过输入细节)。所有终端节点都在原子表中,所有非终端节点都在森林表中,您可以立即看到对象图有多少个节点,即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

比较

稍后将添加部分。

笔记

我目前正在重构包。中心思想和算法保持不变,但新版本将更易于使用,顶级 API 将有所不同。我将很快归档 siberia 并展示重构的版本,我将其称为 objectgraph。敬请期待,它将在本月(2020 年 8 月)发生

啊,和超短版的比较。对于“指针”,我需要与整数一样多的空间,因为我的“指向已经看到的节点的指针”(事实上,指向所有节点,无论是否已经看到)只是整数。在 Crockford 先生的版本中,存储“指针”所需的数量仅受对象图大小的限制。这使得 Crockford 先生版本的最坏情况的复杂性极其可怕。Crockford 先生给了我们“另一个 Bubblesort”。我不是在开玩笑。真是太糟糕了。如果您不相信,有测试,您可以从包的自述文件开始找到它们(也将在本月,2020 年 8 月将它们转换为符合 benchmark.js)

于 2019-11-12T02:04:29.300 回答
3

节省很多,它显示了循环对象的位置。

<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__"]}]}
于 2013-04-22T18:05:27.210 回答
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/

于 2014-03-28T21:31:48.607 回答
1

我也创建了一个 github 项目,它可以序列化循环对象并恢复类,如果你将它保存在 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转换了我的脚本,并且我已经将函数名称从法语更改为英语。

于 2015-03-29T15:44:45.997 回答
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。

于 2014-11-08T11:35:11.870 回答
0

nodejs 模块serialijse 提供了一种很好的方法来处理任何类型的包含循环或 javascript 类实例的 JSON 对象。

const { serialize, deserialize } = require("serialijse");


    var Mary = { name: "Mary", friends: [] };
    var Bob = { name: "Bob", friends: [] };

    Mary.friends.push(Bob);
    Bob.friends.push(Mary);

    var group = [ Mary, Bob];
    console.log(group);

    // testing serialization using  JSON.stringify/JSON.parse
    try {
        var jstr = JSON.stringify(group);
        var jo = JSON.parse(jstr);
        console.log(jo);

    } catch (err) {
        console.log(" JSON has failed to manage object with cyclic deps");
        console.log("  and has generated the following error message", err.message);
    }

    // now testing serialization using serialijse  serialize/deserialize
    var str = serialize(group);
    var so = deserialize(str);
    console.log(" However Serialijse knows to manage object with cyclic deps !");
    console.log(so);
    assert(so[0].friends[0] == so[1]); // Mary's friend is Bob

这个序列化程序支持

  • 对象定义中的循环
  • 类实例的重构
  • 支持 Typed Array、Map 和 Set
  • 在序列化过程中过滤要跳过的属性的能力。
  • 类型数组(Float32Array 等...)的二进制编码以提高性能。
于 2021-05-02T16:06:30.077 回答