Convert Hash to OpenStruct recursively

Given that I have this hash:

h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} } 

And I convert to OpenStruct:

 o = OpenStruct.new(h) => #<OpenStruct a="a", b="b", c={:d=>"d", :e=>"e"}> oa => "a" ob => "b" oc => {:d=>"d", :e=>"e"} 2.1.2 :006 > ocd NoMethodError: undefined method `d' for {:d=>"d", :e=>"e"}:Hash 

I want all nested keys to be methods. Therefore, I can access d as such:

 ocd => "d" 

How can i achieve this?

+13
ruby
source share
4 answers

I personally use the recursive-open-struct gem - it is as simple as RecursiveOpenStruct.new(<nested_hash>)

But for recursion practice, I will show you a fresh solution:

 require 'ostruct' def to_recursive_ostruct(hash) OpenStruct.new(hash.each_with_object({}) do |(key, val), memo| memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val end) end puts to_recursive_ostruct(a: { b: 1}).ab # => 1 

edit

the reason this is better than JSON-based solutions is that you may lose some data when converting to JSON. For example, if you convert a Time object to JSON and then parse it, it will be a string. There are many other examples of this:

 class Foo; end JSON.parse({obj: Foo.new}.to_json)["obj"] # => "#<Foo:0x00007fc8720198b0>" 

yes ... not super helpful. You have completely lost your link to the actual instance.

+12
source share

You can monkey hash class patch

 class Hash def to_o JSON.parse to_json, object_class: OpenStruct end end 

then you can say

 h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} } o = h.to_o ocd # => 'd' 

See Converting a complex nested hash to an object .

+23
source share

I came up with this solution:

 h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} } json = h.to_json => "{\"a\":\"a\",\"b\":\"b\",\"c\":{\"d\":\"d\",\"e\":\"e\"}}" object = JSON.parse(json, object_class:OpenStruct) object.cd => "d" 

For this to work, I had to take an extra step: convert it to json.

+14
source share

Here is a recursive solution that avoids hash conversion to json:

 def to_o(obj) if obj.is_a?(Hash) return OpenStruct.new(obj.map{ |key, val| [ key, to_o(val) ] }.to_h) elsif obj.is_a?(Array) return obj.map{ |o| to_o(o) } else # Assumed to be a primitive value return obj end end 
0
source share

All Articles