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

IT技术 javascript json jsonserializer stringify
2021-01-29 13:56:16

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

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

类型错误:循环对象值

因为我提到的构造。

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

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

6个回答

使用的第二个参数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)))

啊啊好!谢谢,我要试试这个。我找到了由 Douglas Crockford ( github.com/douglascrockford/JSON-js/blob/master/cycle.js )创建的解决方案,但由于我不确定随附的许可证,因此您描述的简单解决方案将是完美的!
2021-03-21 13:56:16
@LoicDuros 许可证是“公共领域”。意思是,你可以用它做任何你想做的事情。
2021-03-21 13:56:16
好答案!我稍微修改了一下,将函数更改为递归函数,以便子对象可以像克隆父对象一样克隆。
2021-03-21 13:56:16
这段代码会产生循环循环,小心使用,很可能会导致您的应用程序崩溃。需要正确的分号并且不能用于事件对象!
2021-03-27 13:56:16
这不仅仅是删除循环引用 - 它只是删除了多次出现的任何内容。除非已经序列化的对象是新对象的“父”,否则不应删除它
2021-04-10 13:56:16

这是一种替代答案,但是由于很多人来这里是为了调试他们的循环对象,并且没有真正的好方法可以在不引入一堆代码的情况下做到这一点,所以这里是。

一个功能,并不像众所周知的JSON.stringify()console.table()只需调用console.table(whatever);,它就会以表格格式将变量记录在控制台中,从而非常容易和方便地阅读变量的内容。

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

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

当你想KEEP循环引用(当你反序列化恢复它们,而不是“的摧毁”),你有2个选择,我会在这里进行比较。首先是道格拉斯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]

美元符号代表根。.bolthave$ref告诉我们这.bolt是一个“已经看到”的对象,并且该特殊属性的值(这里是字符串 $["nut"]["needs"])告诉我们在哪里,请参见===上面的第一个同样对于上面$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

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会有所不同。我将很快存档西伯利亚并展示重构版本,我将其称为 objectgraph。请继续关注,它将在本月(2020 年 8 月)发生

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

我通过以下方式安装了 cycle.js,npm i cycle但出现 TypeError: JSON.decycle is not a function我需要导入 decycle 方法吗?如果是这样,我如何导入它?
2021-03-25 13:56:16

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

<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'如果有人知道如何防止由错误的给定范围构成的很长的计算那么这段代码仍然存在问题,this高兴在这里看到
2021-03-18 13:56:16
这应该是 seen.indexOf(v) != -1
2021-03-26 13:56:16

我创建了一个 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/