Ruby convert Object to Hash
Asked Answered
P

20

156

Let's say I have a Gift object with @name = "book" & @price = 15.95. What's the best way to convert that to the Hash {name: "book", price: 15.95} in Ruby, not Rails (although feel free to give the Rails answer too)?

Perlite answered 17/2, 2011 at 14:56 Comment(4)
Would @gift.attributes.to_options do?Butterfield
1) Is gift a ActiveRecord object? 2)can we assume @name/@price are not just instance variables but also reader accessors? 3) you want only name and price or all the attributes in a gift whatever they are?Bearded
@tokland, 1) no, Gift is exactly like @nash has defined, except, 2) sure, the instance variables can have reader accessors. 3) All the attributes in gift.Perlite
Ok. The question about instance variables/readers access was to know if wanted an outside access (nash) or inside method (levinalex). I updated my answer for the "inside" approach.Bearded
B
87
class Gift
  def initialize
    @name = "book"
    @price = 15.95
  end
end

gift = Gift.new
hash = {}
gift.instance_variables.each {|var| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}

Alternatively with each_with_object:

gift = Gift.new
hash = gift.instance_variables.each_with_object({}) { |var, hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}
Boracic answered 17/2, 2011 at 15:14 Comment(5)
You can use inject to skip initializing the variable: gift.instance_variables.inject({}) { |hash,var| hash[var.to_s.delete("@")] = gift.instance_variable_get(var); hash }Equal
Nice. I replaced var.to_s.delete("@") with var[1..-1].to_sym to get symbols.Perlite
Don't use inject, use gift.instance_variables.each_with_object({}) { |var,hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) } and get rid of the trailing ; hashDoubleganger
I will never understand the ruby fetish for each. map and inject are much more powerful. This is one design qualm I have with Ruby: map and inject are implemented with each. It's simply bad computer science.Demars
Slightly more concise: hash = Hash[gift.instance_variables.map { |var| [var.to_s[1..-1], gift.instance_variable_get(var)] } ]Kelley
D
327

Just say (current object) .attributes

.attributes returns a hash of any object. And it's much cleaner too.

Doughnut answered 2/11, 2012 at 23:49 Comment(3)
Note that this is an ActiveModel-specific method, not a Ruby method.Paratuberculosis
In the case of Sequel -- use .values: sequel.jeremyevans.net/rdoc/classes/Sequel/Model/…Blurt
instance_values can be used for all ruby objects for the similar output.Campanulate
B
87
class Gift
  def initialize
    @name = "book"
    @price = 15.95
  end
end

gift = Gift.new
hash = {}
gift.instance_variables.each {|var| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}

Alternatively with each_with_object:

gift = Gift.new
hash = gift.instance_variables.each_with_object({}) { |var, hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}
Boracic answered 17/2, 2011 at 15:14 Comment(5)
You can use inject to skip initializing the variable: gift.instance_variables.inject({}) { |hash,var| hash[var.to_s.delete("@")] = gift.instance_variable_get(var); hash }Equal
Nice. I replaced var.to_s.delete("@") with var[1..-1].to_sym to get symbols.Perlite
Don't use inject, use gift.instance_variables.each_with_object({}) { |var,hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) } and get rid of the trailing ; hashDoubleganger
I will never understand the ruby fetish for each. map and inject are much more powerful. This is one design qualm I have with Ruby: map and inject are implemented with each. It's simply bad computer science.Demars
Slightly more concise: hash = Hash[gift.instance_variables.map { |var| [var.to_s[1..-1], gift.instance_variable_get(var)] } ]Kelley
V
53

Implement #to_hash?

class Gift
  def to_hash
    hash = {}
    instance_variables.each { |var| hash[var.to_s.delete('@')] = instance_variable_get(var) }
    hash
  end
end


h = Gift.new("Book", 19).to_hash
Vidavidal answered 17/2, 2011 at 16:33 Comment(4)
Technically, it should be .to_hash, since # indicates class methods.Smilax
Actually, no. RDoc documentation says: Use :: for describing class methods, # for describing instance methods, and use . for example code (source: ruby-doc.org/documentation-guidelines.html) Also, official documentation (like the ruby CHANGELOG, github.com/ruby/ruby/blob/v2_1_0/NEWS) uses # for instance methods and the dot for class methods pretty consistently.Vidavidal
Please use inject instead of this antipattern.Crespi
One-liner variant using each_with_object: instance_variables.each_with_object(Hash.new(0)) { |element, hash| hash["#{element}".delete("@").to_sym] = instance_variable_get(element) }Rhynd
C
53
Gift.new.instance_values # => {"name"=>"book", "price"=>15.95}
Clarissaclarisse answered 28/3, 2012 at 15:48 Comment(4)
This is Rails, Ruby itself doesn't have instance_values. Note that Matt asked for a Ruby way, specifically not Rails.Hamburg
He also said feel free to give the Rails answer as well... so I did.Clarissaclarisse
God to see both versions here ;) Liked itElidaelidad
I'd preface your answer with something like "Rails provides the #instance_values method"Ruel
T
23

You can use as_json method. It'll convert your object into hash.

But, that hash will come as a value to the name of that object as a key. In your case,

{'gift' => {'name' => 'book', 'price' => 15.95 }}

If you need a hash that's stored in the object use as_json(root: false). I think by default root will be false. For more info refer official ruby guide

http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

Tantalous answered 27/1, 2017 at 17:10 Comment(0)
T
14

For Active Record Objects

module  ActiveRecordExtension
  def to_hash
    hash = {}; self.attributes.each { |k,v| hash[k] = v }
    return hash
  end
end

class Gift < ActiveRecord::Base
  include ActiveRecordExtension
  ....
end

class Purchase < ActiveRecord::Base
  include ActiveRecordExtension
  ....
end

and then just call

gift.to_hash()
purch.to_hash() 
Thaothapa answered 24/6, 2011 at 19:10 Comment(3)
funny it's not part of the Rails framework. Seems like a useful thing to have there.Schnorrer
The attributes method returns a new hash with the values in - so no need to create another in the to_hash method. Like so: attribute_names.each_with_object({}) { |name, attrs| attrs[name] = read_attribute(name) } . See here: github.com/rails/rails/blob/master/activerecord/lib/…Jeanninejeans
you could have done this with map, your side-effect implementation is hurting my mind man!Demars
B
12
class Gift
  def to_hash
    instance_variables.map do |var|
      [var[1..-1].to_sym, instance_variable_get(var)]
    end.to_h
  end
end
Bearded answered 17/2, 2011 at 16:24 Comment(0)
D
11

If you are not in an Rails environment (ie. don't have ActiveRecord available), this may be helpful:

JSON.parse( object.to_json )
Dissepiment answered 25/9, 2014 at 23:47 Comment(0)
D
7

You can write a very elegant solution using a functional style.

class Object
  def hashify
    Hash[instance_variables.map { |v| [v.to_s[1..-1].to_sym, instance_variable_get v] }]
  end
end
Demars answered 14/4, 2014 at 5:2 Comment(0)
R
6

Recursively convert your objects to hash using 'hashable' gem (https://rubygems.org/gems/hashable) Example

class A
  include Hashable
  attr_accessor :blist
  def initialize
    @blist = [ B.new(1), { 'b' => B.new(2) } ]
  end
end

class B
  include Hashable
  attr_accessor :id
  def initialize(id); @id = id; end
end

a = A.new
a.to_dh # or a.to_deep_hash
# {:blist=>[{:id=>1}, {"b"=>{:id=>2}}]}
Romish answered 26/7, 2013 at 19:23 Comment(1)
Very helpful, thanks you!Garrulous
R
4

You should override the inspect method of your object to return the desired hash, or just implement a similar method without overriding the default object behaviour.

If you want to get fancier, you can iterate over an object's instance variables with object.instance_variables

Rosenfeld answered 17/2, 2011 at 15:3 Comment(0)
V
4

Might want to try instance_values. That worked for me.

Veldaveleda answered 22/8, 2016 at 21:10 Comment(0)
Y
2

To plagiarize @Mr. L in a comment above, try @gift.attributes.to_options.

Yim answered 1/4, 2021 at 19:33 Comment(0)
U
2

You can use symbolize_keys and in-case you have nested attributes we can use deep_symbolize_keys:

gift.as_json.symbolize_keys => {name: "book", price: 15.95}
 
Uterine answered 10/12, 2021 at 13:38 Comment(0)
R
1

Produces a shallow copy as a hash object of just the model attributes

my_hash_gift = gift.attributes.dup

Check the type of the resulting object

my_hash_gift.class
=> Hash
Riker answered 18/11, 2013 at 6:20 Comment(0)
J
0

If you need nested objects to be converted as well.

# @fn       to_hash obj {{{
# @brief    Convert object to hash
#
# @return   [Hash] Hash representing converted object
#
def to_hash obj
  Hash[obj.instance_variables.map { |key|
    variable = obj.instance_variable_get key
    [key.to_s[1..-1].to_sym,
      if variable.respond_to? <:some_method> then
        hashify variable
      else
        variable
      end
    ]
  }]
end # }}}
Jealousy answered 28/5, 2016 at 18:23 Comment(0)
H
0

Gift.new.attributes.symbolize_keys

Heterogamete answered 27/12, 2017 at 10:44 Comment(0)
M
0

To do this without Rails, a clean way is to store attributes on a constant.

class Gift
  ATTRIBUTES = [:name, :price]
  attr_accessor(*ATTRIBUTES)
end

And then, to convert an instance of Gift to a Hash, you can:

class Gift
  ...
  def to_h
    ATTRIBUTES.each_with_object({}) do |attribute_name, memo|
      memo[attribute_name] = send(attribute_name)
    end
  end
end

This is a good way to do this because it will only include what you define on attr_accessor, and not every instance variable.

class Gift
  ATTRIBUTES = [:name, :price]
  attr_accessor(*ATTRIBUTES)

  def create_random_instance_variable
    @xyz = 123
  end

  def to_h
    ATTRIBUTES.each_with_object({}) do |attribute_name, memo|
      memo[attribute_name] = send(attribute_name)
    end
  end
end

g = Gift.new
g.name = "Foo"
g.price = 5.25
g.to_h
#=> {:name=>"Foo", :price=>5.25}

g.create_random_instance_variable
g.to_h
#=> {:name=>"Foo", :price=>5.25}
Manic answered 17/6, 2019 at 19:31 Comment(0)
S
0

I started using structs to make easy to hash conversions. Instead of using a bare struct I create my own class deriving from a hash this allows you to create your own functions and it documents the properties of a class.

require 'ostruct'

BaseGift = Struct.new(:name, :price)
class Gift < BaseGift
  def initialize(name, price)
    super(name, price)
  end
  # ... more user defined methods here.
end

g = Gift.new('pearls', 20)
g.to_h # returns: {:name=>"pearls", :price=>20}
Substitutive answered 20/8, 2019 at 8:12 Comment(0)
E
0

Following Nate's answer which I haven't been able to compile:

Option 1

class Object
    def to_hash
        instance_variables.map{ |v| Hash[v.to_s.delete("@").to_sym, instance_variable_get(v)] }.inject(:merge)
   end
end

And then you call it like that:

my_object.to_hash[:my_variable_name]

Option 2

class Object
    def to_hash
        instance_variables.map{ |v| Hash[v.to_s.delete("@"), instance_variable_get(v)] }.inject(:merge)
   end
end

And then you call it like that:

my_object.to_hash["my_variable_name"]
Extinguish answered 19/4, 2021 at 13:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.