为什么封送处理可以序列化循环引用列表而 json 不能?



这里有一个循环引用列表

2.1.9 :082 > a = []
=> [] 
2.1.9 :083 > a.append(a)
=> [[...]] 

当尝试将a转储为json时,我得到错误

a.to_json
ActiveSupport::JSON::Encoding::CircularReferenceError: object references itself

但当我试图封送它们时,我会得到一个有效的字符串

2.1.9 :085 > Marshal.dump(a)
=> "x04b[x06@x00" 

我只是试图通过再次加载来确保他们正确地转储了价值

b = Marshal.load("x04b[x06@x00")
=> [[...]] 

以下是一些验证,以确保他们正确地将对象转储到字符串

2.1.9 :088 > a.object_id
=> 70257482733700 
2.1.9 :089 > a.first.object_id
=> 70257482733700 
2.1.9 :090 > b.object_id
=> 70257501553000 
2.1.9 :091 > b.first.object_id
=> 70257501553000 
2.1.9 :092 > 

在我的理解中,它们都是将对象转换为字符串,并从字符串中取回对象。我还看到json没有任何结构来引用json的其他部分,这可能是它不能支持这种操作的原因。但是,在json中引入这样的构造来促进当前的情况是否有那么困难。我可能错过了一些关于整理和序列化的更基本的东西,请启发我。

据我所知,他们都在将对象转换为字符串,并从字符串中取回对象。

是。这几乎就是"序列化"或"封送处理"的定义。

我还看到json没有任何结构来引用json的其他部分,这可能是它不能支持这种操作的原因。

是的,这就是原因。

但是在json中引入这样的构造以促进当前的情况吗。

您不能在JSON中引入构造。它被故意设计成没有版本号,这样它就永远无法更改。

当然,这只是意味着我们现在不能添加它,但Doug Crockford在设计JSON时,会从一开始就添加它吗?是的,当然。但他没有。JSON被故意设计得很简单(boldemphasis mine):

JSON不是一种文档格式。它不是一种标记语言。它甚至不是一种通用的序列化格式,因为它没有循环结构的直接表示[…]

例如,请参阅YAML,JSON的超集,它具有引用,因此可以表示周期性数据。

Marshal.dumpto_json都返回一个String,但这几乎是它们的所有共同点。

to_json

to_json根据JSON规范返回一个描述Ruby对象的String。

to_json需要在基本上所有可能的Ruby对象上进行猴子补丁,当在Array上调用时,它会在每个元素上递归调用:

"[#{map { |value| ActiveSupport::JSON.encode(value, options) } * ','}]"

这种递归是您获得的原因

ActiveSupport::JSON::Encoding::CircularReferenceError: object references itself

如果导出成功,在旧的JRuby-on-Rails服务器或PHP服务器上编写的JSON字符串可以由新的Rubinius脚本读取。

倾卸

Marshal.dump返回一个字节流,表示对象本身,以及Ruby如何在内部存储:

Marshal.dump(a).bytes
#=> [4, 8, 91, 6, 64, 0]
Marshal.dump([[]]).bytes
#=> [4, 8, 91, 6, 91, 0]
Marshal.dump([]).bytes
#=> [4, 8, 91, 0]

因此,Marshal.dump按照定义存储a:一个引用自身的单元素数组。

前两个字节是主要版本号和次要版本号。当将转储的对象与相同版本进行比较时,您可以忽略它们:

Marshal.dump(a).bytes.drop(2)
#=> [91, 6, 64, 0]
Marshal.dump([[]]).bytes.drop(2)
#=> [91, 6, 91, 0]

由于表示依赖于Ruby实现,从一个Ruby脚本转储到另一个脚本可能并不总是有效的。

来自文档:

在正常使用中,封送处理只能加载使用主要版本号和相等或更低的次要版本号。

最新更新