Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force Psych to read YAML map as object of given class

I have a class Foo that is supposed to be serialized to a text file in most human-friendly way possible, I use Ruby's default YAML(Psych) with custom encode_with. My question is: if I remove !ruby/object:Foo like so:

def encode_with coder
  coder.tag = nil
  ...
end

can I somehow still force Psych to load a map as object of class Foo(using its init_with). Ideally, I'd like to remove --- document mark as well.

Of course, this is easy to solve with gsub, but I wonder if there is some Psych solution for this. Unfortunately, Psych isn't the best documented of gems.

like image 373
Borsunho Avatar asked Dec 30 '25 23:12

Borsunho


1 Answers

You can provide your own Handler to Psych:

class MyHandler < Psych::Handlers::DocumentStream
  def start_mapping(anchor, tag, implicit, style)
    unless @__root
      tag = "!ruby/hash:MyHash"
      @__root = true
    end
    super anchor, tag, implicit, style
  end
end

class MyHash < Hash
end

def my_parse(yaml)
  parser = Psych::Parser.new(MyHandler.new{|node| return node})
  parser.parse yaml
  false
end

# {a: 1, b: {c: 2, d: 3}, c: [1,2,3]}.to_yaml
str = "---\n:a: 1\n:b:\n  :c: 2\n  :d: 3\n:c:\n- 1\n- 2\n- 3\n"

result = my_parse(str).to_ruby
puts result.class # => MyHash

A bit of documentation. my_parse is just a re-implementation of Psych default parse method. Instead of default handler I use MyHandler here.

MyHandler's start_mapping method overrides TreeBuilder's default implementation. This is a callback which is called when parser bumps into Map in YAML, and document root is a Map. So you just need to swap the tag for the root element (and don't bother with everything else – that's why I use @__root variable to skip further modifications).

like image 59
EugZol Avatar answered Jan 02 '26 15:01

EugZol



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!