我有一个包含子节点的对象(解析树),这些子节点是对其他节点的引用。
我想序列化这个对象,使用JSON.stringify()
,但我得到
TypeError:循环对象值
因为我提到的构造。
我该如何解决这个问题?这些对其他节点的引用是否在序列化对象中表示对我来说并不重要。
另一方面,在创建对象时从对象中删除这些属性似乎很乏味,我不想对解析器(narcissus)进行更改。
我有一个包含子节点的对象(解析树),这些子节点是对其他节点的引用。
我想序列化这个对象,使用JSON.stringify()
,但我得到
TypeError:循环对象值
因为我提到的构造。
我该如何解决这个问题?这些对其他节点的引用是否在序列化对象中表示对我来说并不重要。
另一方面,在创建对象时从对象中删除这些属性似乎很乏味,我不想对解析器(narcissus)进行更改。
使用替换函数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 };
}
当您希望保留循环引用(在反序列化时恢复它们,而不是“核对”它们),您有 2 个选择,我将在此处进行比较。首先是 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]
美元符号代表根。.bolt
have$ref
告诉我们这.bolt
是一个“已经见过”的对象,并且那个特殊属性的值(这里是字符串 $["nut"]["needs"])告诉我们在哪里,见===
上文。上面的第二个$ref
和第二个也是===
如此。
让我们使用一个合适的深度相等测试(即 Anders Kaseorg's deepGraphEqual
function 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())
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)
节省很多,它显示了循环对象的位置。
<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);
你可以在这里找到一个示例小提琴:
我也创建了一个 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转换了我的脚本,并且我已经将函数名称从法语更改为英语。
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。
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
这个序列化程序支持