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

Loi*_*ros 143 javascript json jsonserializer stringify

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

我想序列化这个对象,使用JSON.stringify(),但我得到:JSON.stringify()因为我提到的结构.

我怎么能解决这个问题?对于我来说,序列化对象中是否表示了对其他节点的这些引用并不重要.

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

geo*_*org 205

使用的第二个参数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;
});
Run Code Online (Sandbox Code Playgroud)

http://jsfiddle.net/mH6cJ/38/

正如在其他注释中正确指出的那样,此代码删除了每个"看到"的对象,而不仅仅是"递归"对象.

例如,对于:

a = {x:1};
obj = [a, a];
Run Code Online (Sandbox Code Playgroud)

结果将是不正确的.如果您的结构是这样的,Crockford的decycle是一个更好的选择.

  • @LoicDuros许可证是"公共域名".意思是,你可以随心所欲地做任何事情. (3认同)
  • 这不仅仅消除了循环引用 - 它只是删除多次出现的任何内容.除非已经序列化的对象是新对象的"父",否则不应删除它 (3认同)
  • 啊真好!谢谢,我要试试这个.我找到了Douglas Crockford(https://github.com/douglascrockford/JSON-js/blob/master/cycle.js)创建的解决方案,但由于我不确定它附带的许可证,所以您描述的简单解决方案将会是完美的! (2认同)
  • 此代码会产生循环循环,请小心使用,很可能会导致您的应用程序崩溃。需要正确的分号,并且不可用于事件对象! (2认同)

mat*_*uds 7

以下是具有循环引用的数据结构的示例: 工具棚CY

function makeToolshed(){
    var nut = {name: 'nut'}, bolt = {name: 'bolt'};
    nut.needs = bolt; bolt.needs = nut;
    return { nut: nut, bolt: bolt };
}
Run Code Online (Sandbox Code Playgroud)

当您希望保留循环引用(在反序列化时恢复它们,而不是“破坏”它们)时,您有两种选择,我将在此处进行比较。第一个是 Douglas Crockford 的Cycle.js,第二个是我的siberia包。两者的工作原理都是首先“回收”对象,即构造另一个“包含相同信息”的对象(没有任何循环引用)。

克罗克福德先生首先说道:

JSON.decycle(makeToolshed())
Run Code Online (Sandbox Code Playgroud)

JSON_decycleMakeToolshed

正如你所看到的,JSON 的嵌套结构被保留,但有一个新的东西,那就是具有特殊$ref属性的对象。让我们看看它是如何工作的。

root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]
Run Code Online (Sandbox Code Playgroud)

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

让我们使用合适的深度相等测试(即 Anders Kaseorg 的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
Run Code Online (Sandbox Code Playgroud)

现在,西伯利亚:

JSON.Siberia.forestify(makeToolshed())
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

比较

稍后将添加部分。

笔记

我目前正在重构该包。中心思想和算法保持不变,但新版本将更易于使用,顶层 API 将有所不同。我很快就会归档 siberia 并展示重构版本,我将其称为 objectgraph。请继续关注,本月(2020 年 8 月)将会发生

啊,还有超短版来对比。对于“指针”,我需要与整数占用一样多的空间,因为我的“指向已经看到的节点的指针”(事实上,指向所有节点,无论是否已经看到)只是整数。在 Crockford 先生的版本中,存储“指针”所需的数量仅受对象图大小的限制。这使得克罗克福德先生版本的最坏情况的复杂性变得极其可怕。Crockford 先生给了我们“另一种冒泡排序”。我不是在开玩笑。太糟糕了。如果你不相信,有测试,你可以从包的自述文件开始找到它们(也将在本月,2020 年 8 月,将它们转换为符合 benchmark.js 的标准)


And*_*rew 6

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

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