Unable to deserialize recursively
Hi there! I believe this could be similar to this open issue: https://github.com/aetherknight/recursive-open-struct/issues/69 but I figure I'd make one for our use case.
It seems like we are unable to
irb(main):001:0> require 'recursive-open-struct'
=> true
irb(main):002:0> Marshal.load(Marshal.dump(RecursiveOpenStruct.new(red: [RecursiveOpenStruct.new]))).red
/Users/maple.ong/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:80:in `[]': undefined method `[]' for nil:NilClass (NoMethodError)
elsif v.is_a?(Array) and @options[:recurse_over_arrays]
^^^^^^^^^^^^^^^^^^^^^^
from /Users/maple.ong/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:142:in `block (2 levels) in new_ostruct_member'
from (irb):2:in `<main>'
from /Users/maple.ong/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.3/exe/irb:11:in `<top (required)>'
from /Users/maple.ong/.rbenv/versions/3.1.2/bin/irb:25:in `load'
from /Users/maple.ong/.rbenv/versions/3.1.2/bin/irb:25:in `<main>'
We are trying to upgrade our application to Ruby 3 but we are relying on this behaviour to work. Since it is broken on the latest version we cannot upgrade.
We are seeing the same problem while attempting to upgrade one of our services to Ruby 3 as well. (Thanks for posting this - great timing!)
It looks like marshal_load is passed the options like recurse_over_arrays but they are just passed in at the top level and it will fallback to using OpenStruct's marshal_load implementation which will assign them in as keys in the struct rather than interpreting them as options.
Something like this may help:
class RecursiveOpenStruct
# …
def marshal_load(attributes)
@options ||= {}
@sub_elements ||= {}
self.class.default_options.keys.each do |option|
@options[option] = attributes.delete(option) if attributes.key?(option)
end
@deep_dup = DeepDup.new(@options)
super
end
end
Note: I have never written one of these marshal_load methods before so I'm not confident that this follows all the best practices (it almost certainly doesn't :sweat_smile:). I'm not sure if it's common practice to re-do a lot of the same work that initialize typically does. This would also mean it wouldn't be possible to create a RecursiveOpenStruct that has any of the three option names as a key in the struct. Perhaps if we defined our own marshal_dump method then we could support all key names properly.