我有一个对象(解析树),其中包含对其他节点的引用的子节点。
我想序列化这个对象,使用JSON.stringify()
,但我得到
类型错误:循环对象值
因为我提到的构造。
我该如何解决这个问题?这些对其他节点的引用是否在序列化对象中表示对我来说无关紧要。
另一方面,在创建对象时从对象中删除这些属性似乎很乏味,我不想对解析器(水仙)进行更改。
我有一个对象(解析树),其中包含对其他节点的引用的子节点。
我想序列化这个对象,使用JSON.stringify()
,但我得到
类型错误:循环对象值
因为我提到的构造。
我该如何解决这个问题?这些对其他节点的引用是否在序列化对象中表示对我来说无关紧要。
另一方面,在创建对象时从对象中删除这些属性似乎很乏味,我不想对解析器(水仙)进行更改。
使用的第二个参数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;
});
正如其他评论中正确指出的那样,此代码删除了每个“见过”的对象,而不仅仅是“递归”对象。
例如,对于:
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)))
这是一种替代答案,但是由于很多人来这里是为了调试他们的循环对象,并且没有真正的好方法可以在不引入一堆代码的情况下做到这一点,所以这里是。
一个功能,并不像众所周知的JSON.stringify()
是console.table()
。只需调用console.table(whatever);
,它就会以表格格式将变量记录在控制台中,从而非常容易和方便地阅读变量的内容。
下面是一个带有循环引用的数据结构的例子:
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的嵌套结构被保留了下来,但是有一个新东西,就是具有特殊$ref
属性的对象。让我们看看它是如何工作的。
root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]
美元符号代表根。.bolt
have$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())
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 标准)
节省了很多,它显示了循环对象的位置。
<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__"]}]}
我创建了一个 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);
你可以在这里找到一个示例小提琴: