convert hash to object

33,874

Solution 1

You need to add recursivity:

class Hashit
  def initialize(hash)
    hash.each do |k,v|
      self.instance_variable_set("@#{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
      self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
      self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
    end
  end
end

h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
# => #<Hashit:0x007fa6029f4f70 @a="123r", @b=#<Hashit:0x007fa6029f4d18 @c="sdvs">>

Solution 2

You can use OpenStruct http://ruby-doc.org/stdlib-2.0.0/libdoc/ostruct/rdoc/OpenStruct.html

user = OpenStruct.new({name: "Jimmy Cool", age: "25"})
user.name #Jimmy Cool
user.age #25

Solution 3

Another way is to use JSON and OpenStruct, which are standard ruby libs:

irb:
> require 'JSON'
=> true

> r = JSON.parse({a: { b: { c: 1 }}}.to_json, object_class: OpenStruct)
=> #<OpenStruct a=#<OpenStruct b=#<OpenStruct c=1>>>

> r.a.b.c
=> 1

Solution 4

Ruby has an inbuilt data structure OpenStruct to solve something like this. Still, there is a problem. It is not recursive. So, you can extend OpenStruct class like this:

# Keep this in lib/open_struct.rb
class OpenStruct
  def initialize(hash = nil)
    @table = {}
    if hash
      hash.each_pair do |k, v|
        k = k.to_sym
        @table[k] = v.is_a?(Hash) ? OpenStruct.new(v) : v
      end
    end
  end

  def method_missing(mid, *args) # :nodoc:
    len = args.length
    if mname = mid[/.*(?==\z)/m]
      if len != 1
        raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
      end
      modifiable?[new_ostruct_member!(mname)] = args[0].is_a?(Hash) ? OpenStruct.new(args[0]) : args[0]
    elsif len == 0 # and /\A[a-z_]\w*\z/ =~ mid #
      if @table.key?(mid)
        new_ostruct_member!(mid) unless frozen?
        @table[mid]
      end
    else
      begin
        super
      rescue NoMethodError => err
        err.backtrace.shift
        raise
      end
    end
  end
end

and remember to require 'open_struct.rb' next time you want to use OpenStruct.

Now you can do something like this:

person = OpenStruct.new
person.name = "John Smith"
person.age  = 70
person.more_info = {interests: ['singing', 'dancing'], tech_skills: ['Ruby', 'C++']}

puts person.more_info.interests
puts person.more_info.tech_skills
Share:
33,874

Related videos on Youtube

hehehuhu
Author by

hehehuhu

Updated on October 15, 2020

Comments

  • hehehuhu
    hehehuhu over 3 years

    I am trying to convert hash and nested hashes to objects.

    so far first hash object is converted successfully by this code:

    class Hashit
      def initialize(hash)
        hash.each do |k,v|
          self.instance_variable_set("@#{k}", v)
          self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
          self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
        end
      end
    end
    

    But problem is, i also want to convert nested hash objects. but couldn't make it.

    h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
     => #<Hashit:0x00000006516c78 @a="123r", @b={:c=>"sdvs"}> 
    

    see @b={:c=>"sdvs"} this part in output. I want also to convert it to object. is it possible if yes then how?

    • Cary Swoveland
      Cary Swoveland over 9 years
      If you asking for h to have instance variables [:@a, :@b, :@c], as @Ben and I assumed, the answer you selected is incorrect.
  • hehehuhu
    hehehuhu over 9 years
    @mohameddiaa27 your answer was also helpful but openstruct is good but different thing.
  • Cary Swoveland
    Cary Swoveland over 9 years
    h.instance_variables => [:@a, :@b]. Do we not want => [:@a, :@b, :@c]?
  • Cary Swoveland
    Cary Swoveland over 9 years
    Nice one, Ben, though I think the OP's way of defining separate read and write accessors is unnecessarily complex, and does not read as well as just self.class.send(:attr_accessor, k).
  • kaibakker
    kaibakker almost 8 years
    This is the best answer!
  • Sergio Belevskij
    Sergio Belevskij about 7 years
    OpenStruct.new({... not so deep, as this perfect example.
  • Patrick Dougall
    Patrick Dougall over 6 years
    Will also coerce your values to string or int :(
  • Allen
    Allen over 6 years
    This way is not recursive. You can't object.a.b.c, you can only object.a.
  • Mark Schneider
    Mark Schneider about 2 years
    Does not work for keys to which Hash class responds. For instance, try: r = JSON.parse({a: { b: { display: 1 }}}.to_json, object_class: OpenStruct)