我有一个对象(parse tree),它包含引用其他节点的子节点。
我想使用JSON.stringify()
来序列化这个对象,但是我得到了
TypeError:循环对象值
因为我提到的构造。
我该如何解决这个问题呢?这些对其他节点的引用是否在序列化对象中表示对我来说并不重要。
另一方面,在创建这些属性时将它们从对象中删除似乎很乏味,而且我不想对解析器(narcissus)进行更改。
发布于 2012-02-22 01:41:17
使用stringify
的第二个参数replacer function来排除已经序列化的对象:
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];
结果将是不正确的。如果你的结构是这样的,你可能想要使用克罗克福德的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)))
发布于 2020-07-11 01:26:02
这是一种替代答案,但由于许多人来这里的目的是调试他们的循环对象,而没有一种很好的方法可以在不引入大量代码的情况下做到这一点,所以这里就是这样。
console.table()
是一个没有JSON.stringify()
那么出名的特性。只需调用console.table(whatever);
,它将以表格格式将变量记录在控制台中,这使得仔细阅读变量的内容变得相当容易和方便。
发布于 2019-11-12 10:04:29
下面是一个具有循环引用的数据结构示例:
function makeToolshed(){
var nut = {name: 'nut'}, bolt = {name: 'bolt'};
nut.needs = bolt; bolt.needs = nut;
return { nut: nut, bolt: bolt };
}
当你想保持循环引用()(在反序列化时恢复它们,而不是“‘ll”它们),你有两个选择,我将在这里进行比较。首先是Douglas Crockford的cycle.js,其次是我的siberia包。两者的工作方式都是首先“解循环”对象,即构造“包含相同信息”的另一个对象(没有任何循环引用)。
克罗克福德先生先说:
JSON.decycle(makeToolshed())
如您所见,JSON的嵌套结构保持不变,但有了一个新东西,即具有特殊$ref
属性的对象。让我们看看它是如何工作的。
root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]
美元符号代表词根。具有$ref
的坚果告诉我们.bolt
是一个“已经看到”的对象,而这个特殊属性的值(这里是字符串$“.bolt
”)告诉我们在哪里,请参见上面的第一个===
。上面的第二个$ref
和第二个===
也是如此。
让我们使用合适的深度相等性测试(即Anders Kaseorg对this question的公认答案中的deepGraphEqual
函数)来查看克隆是否有效。
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,没有嵌套结构。对象图是以“扁平”的方式描述的。对象图的每个节点都被转换成一个扁平树(纯键值对列表,它是.forest.
中索引为0的条目),我们找到根对象,在更高的索引中,我们找到对象图的其他节点,负值(森林的某些树的某个键的负值)指向atoms
数组(它是通过类型数组键入的,但我们在这里跳过键入的细节)。所有终端节点都在atoms表中,所有非终端节点都在森林表中,您可以立即看到对象图有多少个节点,即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先生的版本中,存储“指针”所需的量仅受对象图大小的限制。这使得克罗克福德的版本在最坏的情况下变得极其复杂。克罗克福德先生给了我们“另一个泡泡”。我没跟你开玩笑。真的很糟糕。如果你不相信,有一些测试,你可以从软件包的自述文件中找到它们(将在这个月,2020年8月将它们转换为benchmark.js兼容)
https://stackoverflow.com/questions/9382167
复制相似问题