74rajw
Last Updated: November 17, 2017
·
14.46K
· MeetDom
934794545f4c74ad8a6bcf9939e764a4

Convert a complex nested hash to an object

Sometimes in Ruby we need to convert a Hash into an object. Instead of accessing keys in Hash, we want to access attributes (methods) on an object. OpenStruct provides an easy way for us to do this: OpenStruct.new(hash).

But what do we do if we have a complex nested Hash with Arrays etc. OpenStruct doesn't recurse over Arrays and only works well with a flat structure. I've come across a number of Gems that attempt to solve this problem, and I've been using one in particular for the past year: recursive-open-struct. While it worked well in my particular scenario, others have expressed problems. Since I was refactoring the code that made use of this Gem, I thought I take a look at trying to remove the dependency.

I discovered a simpler way to achieve the same thing without the dependency on a Gem, and with a lot less code, and I thought I might share it with the community because I think it's pretty nifty.

We can get JSON to do the heavy lifting for us and instruct it to coerce nested attributes into OpenStructs. It will also remain respectful of Arrays.

Since both JSON and OpenStruct are in the Ruby Standard Library, we'll have no third-party dependencies.

First we need to require these two libraries:

require 'json'
require 'ostruct'

Let's create a complex nested Hash with Arrays:

hash = {:a=>1,:b=>[{:c=>2,:d=>[{:e=>3,:f=>4},{:e=>5,:f=>6}]},{:c=>4,:d=>[{:e=>7,:f=>8},{:e=>9,:f=>10}]},{:c=>6,:d=>[{:e=>11,:f=>12},{:e=>13,:f=>14}]}]}

Now let's convert the Hash to JSON:

json = hash.to_json
# => "{\"a\":1,\"b\":[{\"c\":2,\"d\":[{\"e\":3,\"f\":4},{\"e\":5,\"f\":6}]},{\"c\":4,\"d\":[{\"e\":7,\"f\":8},{\"e\":9,\"f\":10}]},{\"c\":6,\"d\":[{\"e\":11,\"f\":12},{\"e\":13,\"f\":14}]}]}"

Finally, let's parse the JSON instructing it to coerce objects with attributes into OpenStructs:

object = JSON.parse(json, object_class: OpenStruct)
# => #<OpenStruct a=1, b=[#<OpenStruct c=2, d=[#<OpenStruct e=3, f=4>, #<OpenStruct e=5, f=6>]>, #<OpenStruct c=4, d=[#<OpenStruct e=7, f=8>, #<OpenStruct e=9, f=10>]>, #<OpenStruct c=6, d=[#<OpenStruct e=11, f=12>, #<OpenStruct e=13, f=14>]>]>

Let's starting testing the object that's been created:

object.a
# => 1

As expected; now let's have a look at b:

object.b
# => [#<OpenStruct c=2, d=[#<OpenStruct e=3, f=4>, #<OpenStruct e=5, f=6>]>, #<OpenStruct c=4, d=[#<OpenStruct e=7, f=8>, #<OpenStruct e=9, f=10>]>, #<OpenStruct c=6, d=[#<OpenStruct e=11, f=12>, #<OpenStruct e=13, f=14>]>]

object.b.class
# => Array

object.b.size
# => 3

Since b is an array, we can access it's elements using an index:

object.b[0].class
# => OpenStruct

And each element is an object, so we can delve further to access it's attributes:

object.b[0].c
# => 2
object.b[0].d
# => [#<OpenStruct e=3, f=4>, #<OpenStruct e=5, f=6>]
object.b[0].d.class
# => Array
object.b[0].d.size
# => 2
object.b[0].d[1]
# => #<OpenStruct e=5, f=6>
object.b[0].d[1].f
# => 6

I love this little snippet of code. What was once achieved using a complex piece of code packaged into a Gem for reuse, is now 4 or 5 lines of code.

I hope you find this as useful as I did.

Say Thanks
Respond

3 Responses
Add your response

18916
4693d7cfa88635d430c0de9a92f8dd84

wow this is great! Gotta love little hidden treats like this. Thank you so much!

over 1 year ago ·
29242

So basically

# /config/initializers/hash.rb

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

Then you can call it anywhere in your project like

hash = { format: { html?: true }}

hash.to_o.format.html? # => true
4 months ago ·
29433

This is wonderful! I will try that definitely with a large dataset I have in a deeply nested Json format

28 days ago ·