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

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

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

循环对象值

因为我提到的构造。

我该怎么办?对我来说,这些对其他节点的引用是否在序列化对象中表示并不重要。

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

167608 次浏览

使用 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];

结果将是不正确的。如果你的结构是这样的,你可能需要使用 Crocford 的 自行车或者这个(更简单的)函数,它只是用 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)))

它显示了一个 周期对象的位置。

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

你可以在这里找到一个小提琴的例子:

Http://jsfiddle.net/hoff97/7uyd4/

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.201412:30:13,1095]]1095减少到095。

我还创建了一个 github 项目,它可以序列化循环对象,并且如果您将类保存在 seralizename 属性(如 String)中,则可以还原该类

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和我已经改变了函数名从法语到英语。

下面是一个具有循环引用的数据结构示例: toolshedCY

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

当您希望 强 > KEEP 强循环引用(在反序列化时恢复它们,而不是“核爆”它们)时,有两种选择,我将在这里比较。第一个是道格拉斯·克罗克福特的 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]

美元符号代表根。使用 $ref.bolt告诉我们,.bolt是一个“已经看到”的对象,而这个特殊属性的值(在这里,字符串 $[“ nut”][“ need”])告诉我们在哪里,参见上面的第一个 ===。对于第二个 $ref和上面的第二个 ===也是如此。

让我们使用一个合适的深度相等测试(即 AndersKaseorg 的 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_Siberia_forestify_makeToolshed

西伯利亚没有试图模仿“经典”的 JSON,没有嵌套的结构。 对象图的每个节点都被转换成一个平面树(只包含整数值的普通键值对列表) ,这是 .forest.中的一个条目。在索引0处,我们找到根对象,在更高的索引处,我们找到对象图的其他节点,负值(森林中某个树的某个键)指向 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月)发生

啊,还有超短版的比较。对于“指针”,我需要整数所占用的空间,因为我的“指向已经看到的节点”(事实上,指向所有节点,不管是否已经看到) 只是整数。在克罗克福德先生的版本中,存储一个“指针”所需的数量只受对象图的大小的限制。这使得克罗克福德先生的版本 非常可怕的最坏情况的复杂性。克罗克福德先生给了我们“另一个泡泡糖”。我没开玩笑。就是这么糟糕。如果您不相信,有测试,您可以从包的自述文件开始找到它们(将在本月,2020年8月,将它们转换为与 benchmark.js 兼容)

这是一个替代的答案,但是因为很多人来这里是为了调试他们的圆形对象,并且没有一个真正好的方法来做到这一点,如果没有一大堆的代码,在这里。

有一个特性不像 JSON.stringify()那样广为人知,那就是 console.table()。只需调用 console.table(whatever);,它就会在控制台中以表格格式记录变量,这使得阅读变量的内容变得相当容易和方便。

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


此序列化程序支持

  • 对象定义中的循环
  • 类实例的重构
  • 支持类型化数组、映射和集合
  • 在序列化过程中筛选要跳过的属性的能力。
  • 类型数组(Float32Array 等)的二进制编码,以提高性能。