Saturday, March 31, 2012

Initialize Objects from a hierarchy of hashes (JSON format) in Ruby

There are some nice little functions to initialize Objects in Ruby from a hash. A good example could be found here.

But sometimes, when we read an external API (XML or JSON) and parse the result, we get data which is a set of nested hashes and arrays. Then we want to initialize an object in our application with this data. I wrote the following module to do that.

init_from_hash.rb
-----------------------------
module InitFromHash
  
  def initialize(*args)
    args.first.each do |k, v| 
      unless defined?(k).nil?    # check if it's included as a reader attribute
        result = v.instance_of?(Array) ? v.inject([]) {|arr, v1| arr << init_object(v1, k)} : init_object(v, k)
        instance_variable_set("@#{k}", result)
      end
    end if (args.length == 1 && args.first.is_a?(Hash)) 
  end
  
  def init_object value, klass
     value.instance_of?(Hash) ? (get_module_name + klass.to_s.capitalize).constantize.new(value) : value
  end
  
  def get_module_name
    (self.class.name =~ /^(.+::).+$/) ? $1 : ''
  end
    
end


Assumptions:
1. Our object also contains nested objects. For example, we have an author who has articles, and each article has comments.
2. Names of our classes match the keys in our data.
3. All the classes of objects to be initialized have the same module name.
4. We want only attributes specified in the reader_attr to be present

Example of usage:
Module Blog
  class Author
      include InitFromHash
      attr_reader :name, :article
  end
end

module Blog
  class Article
      include InitFromHash
      attr_reader :name, :abstract, :comment
  end
end

module Blog
  class Comment
      include InitFromHash
      attr_reader :text
  end
end


When we receive our data in the form:
data = {"author" => 'Mike', "article" => [{"name" => "Is there life on Mars",
"abstract" => "Probably", "comment" => [{"text" => "Really?"},
{"text" => "No way!"}]}, {"name" => "Milk is good for you", 
"abstract" => "Scientists discovered", "comment" => [{"text" => "Really?"},
{"text" => "No way!"}] }}


Then we can initialize our instance as simple as
author = Author.new(data)

No comments:

Post a Comment