4

如何在 Psych 中反序列化以返回现有对象,例如类对象?

做一个类的序列化,我可以做

require "psych"

class Class
  yaml_tag 'class'
  def encode_with coder
    coder.represent_scalar 'class', name
  end
end

yaml_string = Psych.dump(String) # => "--- !<class> String\n...\n" 

但是如果我尝试这样做Psych.load,我会得到一个匿名类,而不是 String 类。

正常的反序列化方法是Object#init_with(coder),但这只会改变现有匿名类的状态,而我想要 String 类。

Psych::Visitors::ToRuby#visit_Psych_Nodes_Scalar(o)在某些情况下,他们不是用 修改现有对象,而是init_with确保首先创建正确的对象(例如,调用Complex(o.value)以反序列化复数),但我认为我不应该对该方法进行修补。

我注定要使用低级或中级发射,还是我错过了什么?

背景

我将描述这个项目,为什么它需要类,以及为什么它需要(反)序列化。

项目

Small Eigen Collider 旨在为 Ruby 创建随机任务以运行。最初的目的是查看 Ruby 的不同实现(例如,Rubinius 和 JRuby)在给定相同的随机任务时是否返回相同的结果,但我发现它对于检测 Rubinius 和 YARV 的段错误也很有用。

每个任务由以下内容组成:

receiver.send(method_name, *parameters, &block)

其中receiver是随机选择的对象,method_name是随机选择的方法的名称,*parameters是随机选择的对象的数组。&block不是很随机 - 它基本上相当于{|o| o.inspect}.

例如,如果接收者是“a”,method_name 是 :casecmp,参数是 [“b”],那么您将调用

"a".send(:casecmp, "b") {|x| x.inspect}

这相当于(因为该块是不相关的)

"a".casecmp("b")

Small Eigen Collider 运行此代码,并记录这些输入以及返回值。在此示例中,Ruby 的大多数实现返回 -1,但在某个阶段,Rubinius 返回 +1。(我将此作为错误提交https://github.com/evanphx/rubinius/issues/518并且 Rubinius 维护人员修复了该错误)

为什么需要课程

我希望能够在我的 Small Eigen Collider 中使用类对象。通常,它们将是接收器,但它们也可以是参数之一。

例如,我发现 YARV 段错误的一种方法是

Thread.kill(nil)

在这种情况下,receiver 是类对象 Thread,parameters 是 [nil]。(错误报告:http ://redmine.ruby-lang.org/issues/show/4367 )

为什么需要(反)序列化

Small Eigen Collider 需要序列化有几个原因。

一是每次使用随机数生成器生成一系列随机任务是不切实际的。JRuby 有一个不同的内置随机数生成器,所以即使给定相同的 PRNG 种子,它也会给 YARV 提供不同的任务。相反,我所做的是我创建一个随机任务列表一次(第一次运行 ruby​​ bin/small_eigen_collider),让初始运行将任务列表序列化为 tasks.yml,然后运行程序的后续运行(使用不同的Ruby 实现)读取该 tasks.yml 文件以获取任务列表。

我需要序列化的另一个原因是我希望能够编辑任务列表。如果我有一长串导致分段错误的任务,我想将列表减少到导致分段错误所需​​的最小值。例如,使用以下错误 https://github.com/evanphx/rubinius/issues/643

ObjectSpace.undefine_finalizer(:symbol)

本身不会导致分段错误,也不会

Symbol.all_symbols.inspect

但如果你把两者放在一起,它确实如此。但我一开始有数千个任务,需要将其缩减为仅这两个任务。

在这种情况下,返回现有类对象的反序列化是否有意义,或者您认为有更好的方法吗?

4

2 回答 2

1

我目前的研究现状:

要使您想要的行为正常工作,您可以使用我上面提到的解决方法。

这是格式良好的代码示例:

string_yaml  = Psych.dump(Marshal.dump(String))
  # => "--- ! \"\\x04\\bc\\vString\"\n"
string_class = Marshal.load(Psych.load(string_yaml))
  # => String

你修改类的技巧可能永远不会奏效,因为真正的类处理没有在 psych/yaml 中实现。

您可以使用这个 repo的软性库,它是独立的库。

(宝石:心理- 加载它,使用:gem 'psych'; require 'psych'并检查Psych::VERSION

正如您在第 249-251 行中看到的,未处理匿名类 Class 的对象处理。

我建议您通过扩展此类处理来为 Psych 库做出贡献,而不是猴子修补类。

所以在我看来,最终的 yaml 结果应该是这样的:"--- !ruby/class String"

经过一晚上的思考,我可以说,这个功能真的很棒!


更新

找到了一个似乎以预期方式工作的微小解决方案:

代码要点:gist.github.com/1012130(带有描述性注释)

于 2011-06-07T09:10:53.273 回答
1

Psych 维护者已经实现了类模块的序列化和反序列化。它现在在 Ruby 中!

于 2011-06-09T13:38:16.717 回答