こしごぇ(B)

旧:http://d.hatena.ne.jp/koshigoeb/

rubyのYAMLによるシリアライズについて

全く中身を把握していなかった事に気づかされたので、少しだけ調べてみました。

以下は Object#to_yaml のソース。

# yaml/rubytypes.rb
class Object
  yaml_as "tag:ruby.yaml.org,2002:object"
  def to_yaml_style; end
  def to_yaml_properties; instance_variables.sort; end
  def to_yaml( opts = {} )
    YAML::quick_emit( self, opts ) do |out|
      out.map( taguri, to_yaml_style ) do |map|
        to_yaml_properties.each do |m|
          map.add( m[1..-1], instance_variable_get( m ) )
        end
      end
    end
  end
end

クラス名とインスタンス変数を記録している様です。

(001): >> require 'yaml'
=> true
(002): >> class Hoge
  def initialize(a, b, c)
    @a, @b, @c = a, b, c
  end
end
=> nil
(007): >> puts Hoge.new(1, 2, 3).to_yaml
--- !ruby/object:Hoge
a: 1
b: 2
c: 3
=> nil
(008): >> YAML.load(Hoge.new(1, 2, 3).to_yaml).instance_eval{ p @a, @b, @c }
1
2
3
=> nil

確かに、インスタンス変数が記録され、復元するとその値がインスタンス変数にセットされています。

肝心の復元方法ですが、ぱっとソースを眺めた感じよく分かりませんでした。挙動から想像する限り、Class.allocate して instance_variable_set とかしてるんだろうと思います。
以下は allocate で grep した結果見つかったソースです。

    def YAML.object_maker( obj_class, val )
        if Hash === val
            o = obj_class.allocate
            val.each_pair { |k,v|
                o.instance_variable_set("@#{k}", v)
            }
            o
        else
            raise YAML::Error, "Invalid object explicitly tagged !ruby/Object: " + val.inspect
        end
    end

ユーザ定義のクラスをYAMLシリアライズする場合、allocateによるインスタンス化とインスタンス変数の代入によってオブジェクトが復元出来る様にしておく必要があるという事でしょうか。

そうだとすれば、initialize による初期化処理が必要な場合に困る事があるのでしょうかね?