61

I have a Ruby hash which looks like:

{ "id" => "123", "name" => "test" }

I would like to convert it to:

{ :id => "123", :name => "test" }
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
ed1t
  • 8,719
  • 17
  • 67
  • 110
  • [dup?](http://stackoverflow.com/q/800122/119790) – Ian Vaughan May 11 '12 at 14:39
  • 2
    @IanVaughan Recursion! – tckmn May 12 '13 at 00:06
  • 3
    Another option is to use ActiveSupport's [`HashWithIndifferentAccess`](http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html). Then you never have to worry about converting the hash, you can use a symbol to retrieve a value, even if some keys are inserted as strings. If you're not using Rails then you'll have to `include 'active_support/core_ext/hash/indifferent_access'`. – Mark Thomas May 06 '15 at 11:56

15 Answers15

73
hash = {"apple" => "banana", "coconut" => "domino"}
Hash[hash.map{ |k, v| [k.to_sym, v] }]
#=> {:apple=>"banana", :coconut=>"domino"}

@mu is too short: Didn't see word "recursive", but if you insist (along with protection against non-existent to_sym, just want to remind that in Ruby 1.8 1.to_sym == nil, so playing with some key types can be misleading):

hash = {"a" => {"b" => "c"}, "d" => "e", Object.new => "g"}

s2s = 
  lambda do |h| 
    Hash === h ? 
      Hash[
        h.map do |k, v| 
          [k.respond_to?(:to_sym) ? k.to_sym : k, s2s[v]] 
        end 
      ] : h 
  end

s2s[hash] #=> {:d=>"e", #<Object:0x100396ee8>=>"g", :a=>{:b=>"c"}}
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Victor Moroz
  • 9,167
  • 1
  • 19
  • 23
  • I like this version as long as the keys understand `to_sym`. You might get asked for a recursive version though. – mu is too short Dec 05 '11 at 07:05
  • this doesn't work if you have a hash within an array: {"a"=>{"b"=>"c"}, "d"=>[{"f"=>"e"}]} see the last element "d". I am going to try to figure this out, but help/answers are welcome. I can understand this may not work for other objects, but I think arrays like this are common? – nilanjan May 01 '13 at 02:17
  • @nilanjan seems to me that it shouldn't in this context; if that functionality were required I would argue a second method should be created, as you would essentially be treating that nested array as if it were partially flattened. – Chris Tonkinson Feb 07 '14 at 22:57
59

If you happen to be in Rails then you'll have symbolize_keys:

Return a new hash with all keys converted to symbols, as long as they respond to to_sym.

and symbolize_keys! which does the same but operates in-place. So, if you're in Rails, you could:

hash.symbolize_keys!

If you want to recursively symbolize inner hashes then I think you'd have to do it yourself but with something like this:

def symbolize_keys_deep!(h)
  h.keys.each do |k|
    ks    = k.to_sym
    h[ks] = h.delete k
    symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
  end
end

You might want to play with the kind_of? Hash to match your specific circumstances; using respond_to? :keys might make more sense. And if you want to allow for keys that don't understand to_sym, then:

def symbolize_keys_deep!(h)
  h.keys.each do |k|
    ks    = k.respond_to?(:to_sym) ? k.to_sym : k
    h[ks] = h.delete k # Preserve order even when k == ks
    symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
  end
end

Note that h[ks] = h.delete k doesn't change the content of the Hash when k == ks but it will preserve the order when you're using Ruby 1.9+. You could also use the [(key.to_sym rescue key) || key] approach that Rails uses in their symbolize_keys! but I think that's an abuse of the exception handling system.

The second symbolize_keys_deep! turns this:

{ 'a' => 'b', 'c' => { 'd' => { 'e' => 'f' }, 'g' => 'h' }, ['i'] => 'j' }

into this:

{ :a => 'b', :c => { :d => { :e => 'f' }, :g => 'h' }, ['i'] => 'j' }

You could monkey patch either version of symbolize_keys_deep! into Hash if you really wanted to but I generally stay away from monkey patching unless I have very good reasons to do it.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • +1 Also if @ed1t doesn't user Rails, it's easy to see hot it's implemented there – MBO Dec 05 '11 at 00:09
  • is there an option to call symbolize_keys recursively? – ed1t Dec 05 '11 at 01:02
  • @ed1t: I think you'd have to do the recursive version by hand but it isn't terribly difficult, please see my update. – mu is too short Dec 05 '11 at 01:22
  • 1
    You do have a recursive variant in the Rails API with `deep_symbolize_keys`: http://api.rubyonrails.org/classes/Hash.html#method-i-deep_symbolize_keys – tirdadc Apr 22 '16 at 15:51
  • 1
    Method symbolize_keys is deprecated and will be removed in Rails 5.1 – Rimian Aug 30 '16 at 02:15
  • Do you have a reference for that @Rimian? – mu is too short Aug 30 '16 at 04:11
  • Sorry, I can't find it. You can produce the warning by running RSpec.The warning is: DEPRECATION WARNING: Method symbolize_keys is deprecated and will be removed in Rails 5.1, as `ActionController::Parameters` no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.0.1/classes/ActionController/Parameters.html – Rimian Aug 30 '16 at 09:56
  • 1
    I think you're misreading that error message @Rimian. That's talking about trying to `params.symbolize_keys` (hence the `ActionController::Parameters`), I don't see any evidence that `Hash#symbolize_keys` is deprecated just that `params.is_a? Hash` won't be true in 5.1. – mu is too short Aug 30 '16 at 21:22
46

If you are using Rails >= 4 you can use:

hash.deep_symbolize_keys
hash.deep_symbolize_keys!

or

hash.deep_stringify_keys
hash.deep_stringify_keys!

see http://apidock.com/rails/v4.2.1/Hash/deep_symbolize_keys

Daniel777
  • 851
  • 8
  • 20
MRifat
  • 621
  • 5
  • 7
22

Just in case you are parsing JSON, from the JSON docs you can add the option to symbolize the keys upon parsing:

hash = JSON.parse(json_data, symbolize_names: true)
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Alexander Popov
  • 23,073
  • 19
  • 91
  • 130
12

Victor Moroz provided a lovely answer for the simple recursive case, but it won't process hashes that are nested within nested arrays:

hash = { "a" => [{ "b" => "c" }] }
s2s[hash] #=> {:a=>[{"b"=>"c"}]}

If you need to support hashes within arrays within hashes, you'll want something more like this:

def recursive_symbolize_keys(h)
  case h
  when Hash
    Hash[
      h.map do |k, v|
        [ k.respond_to?(:to_sym) ? k.to_sym : k, recursive_symbolize_keys(v) ]
      end
    ]
  when Enumerable
    h.map { |v| recursive_symbolize_keys(v) }
  else
    h
  end
end
Community
  • 1
  • 1
pje
  • 21,801
  • 10
  • 54
  • 70
7

If you're using Rails (or just Active Support):

{ "id" => "123", "name" => "test" }.symbolize_keys
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
bonkydog
  • 2,012
  • 20
  • 11
7

Starting with Ruby 2.5 you can use the transform_key method.

So in your case it would be:

h = { "id" => "123", "name" => "test" }
h.transform_keys!(&:to_sym)      #=> {:id=>"123", :name=>"test"}

Note: the same methods are also available on Ruby on Rails.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Paulo Fidalgo
  • 21,709
  • 7
  • 99
  • 115
7

Try this:

hash = {"apple" => "banana", "coconut" => "domino"}
 # => {"apple"=>"banana", "coconut"=>"domino"} 

hash.tap do |h|
  h.keys.each { |k| h[k.to_sym] = h.delete(k) }
end
 # => {:apple=>"banana", :coconut=>"domino"} 

This iterates over the keys, and for each one, it deletes the stringified key and assigns its value to the symbolized key.

John Feminella
  • 303,634
  • 46
  • 339
  • 357
  • I also have arrays and hash inside of the object. How can I apply this recursilvely? – ed1t Dec 05 '11 at 00:10
  • 1
    I like this answer but I don't understand the purpose of tap – pguardiario Dec 05 '11 at 00:48
  • 1
    They both produce the same end result. However, if we didn't use #tap, then the value of the expression would be the list of modified keys (`h.keys`), which is somewhat confusing. I used #tap so that the result is the hash rather than the array of keys. It's just for illustrative purposes. – John Feminella Dec 05 '11 at 13:19
6

Here's a Ruby one-liner that is faster than the chosen answer:

hash = {"apple" => "banana", "coconut" => "domino"}
#=> {"apple"=>"banana", "coconut"=>"domino"}

hash.inject({}){|h,(k,v)| h[k.intern] = v; h}
#=> {:apple=>"banana", :coconut=>"domino"}

Benchmark results:

n = 100000

Benchmark.bm do |bm|
  bm.report { n.times { hash.inject({}){|h,(k,v)| h[k.intern] = v; h} } }
  bm.report { n.times { Hash[hash.map{ |k, v| [k.to_sym, v] }] } }
end

# =>       user     system      total        real
# =>   0.100000   0.000000   0.100000 (  0.107940)
# =>   0.120000   0.010000   0.130000 (  0.137966)
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Chakaitos
  • 116
  • 1
  • 10
  • 1
    Just curious... Where did the `k.intern` property come from? Your snippet doesn't work because of it and because I'm not a pro Ruby-er the snippet is so damn efficient that it's difficult to make sense of. – Pierce Mar 12 '15 at 04:59
  • 1
    k.intern is turning the keys to symbols. k.intern is also equal to k.to_sym. I hope this answers your question – Chakaitos Mar 12 '15 at 05:08
  • 1
    Thank you for that clarification! You are completely right. I did what I should have done in the first place (looked at documentation) and found the `String.intern` method. It failed for me because it was running the `k.intern` property on a `symbol` instead. http://ruby-doc.org/core-2.2.0/String.html#method-i-intern – Pierce Mar 16 '15 at 19:06
2

You can also extend core Hash ruby class placing a /lib/hash.rb file :

class Hash
  def symbolize_keys_deep!
    new_hash = {}
    keys.each do |k|
      ks    = k.respond_to?(:to_sym) ? k.to_sym : k
      if values_at(k).first.kind_of? Hash or values_at(k).first.kind_of? Array
        new_hash[ks] = values_at(k).first.send(:symbolize_keys_deep!)
      else
        new_hash[ks] = values_at(k).first
      end
    end

    new_hash
  end
end

If you want to make sure keys of any hash wrapped into arrays inside your parent hash are symbolized, you need to extend also array class creating a "array.rb" file with that code :

class Array
  def symbolize_keys_deep!
    new_ar = []
    self.each do |value|
      new_value = value
      if value.is_a? Hash or value.is_a? Array
        new_value = value.symbolize_keys_deep!
      end
      new_ar << new_value
    end
    new_ar
  end
end

This allows to call "symbolize_keys_deep!" on any hash variable like this :

myhash.symbolize_keys_deep!
David Fabreguette
  • 972
  • 1
  • 8
  • 14
2

I'm partial to:

irb
ruby-1.9.2-p290 :001 > hash = {"apple" => "banana", "coconut" => "domino"}
{
      "apple" => "banana",
    "coconut" => "domino"
}
ruby-1.9.2-p290 :002 > hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
{
      :apple => "banana",
    :coconut => "domino"
}

This works because we're iterating over the hash and building a new one on the fly. It isn't recursive, but you could figure that out from looking at some of the other answers.

hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • 3
    `each_with_object` would probably be a better call than `inject` in this case (unless of course `each_with_object` wasn't available). – mu is too short Dec 05 '11 at 19:05
  • @muistooshort, well, it's almost a wash between the two. I prefer `inject`, just because I always use it. From looking at the source of the two, `inject` is bigger, but that doesn't necessarily mean slower, or slow enough to make a real difference. The reversed parameters and lack of a need to return the `h` value will cause a pause in my thinking to use it, so is there some compelling reason to use `each_with_object`? Maybe some benchmark somewhere showing `inject` getting smoked? – the Tin Man Dec 06 '11 at 17:32
  • I tend to use `inject` when I'm interested in the block's return value (`a.inject(:+)` and such) and `each_with_object` when I'd have to add the `; h` bit to the block or when I'm interesting in "iteration with some baggage" rather than simple injection. The block argument order difference between them is a little perverse. I find that the block return value gets visually lost over in the right side. Or maybe I'm just opinionated :) – mu is too short Dec 06 '11 at 17:53
  • 1
    No, you're not opinionated, I think reversing the parameters was a bad design choice but we're stuck with it now. – the Tin Man Dec 06 '11 at 19:27
1
def symbolize_keys(hash)
   new={}
   hash.map do |key,value|
        if value.is_a?(Hash)
          value = symbolize_keys(value) 
        end
        new[key.to_sym]=value
   end        
   return new

end  
puts symbolize_keys("c"=>{"a"=>2,"k"=>{"e"=>9}})
#{:c=>{:a=>2, :k=>{:e=>9}}}
1

Here's my two cents,

my version of symbolize_keys_deep! uses the original symbolize_keys! provided by rails and just makes a simple recursive call to Symbolize sub hashes.

  def symbolize_keys_deep!(h)
    h.symbolize_keys!
    h.each do |k, v|
      symbolize_keys_deep!(v) if v.is_a? Hash
    end
  end
Don Giulio
  • 2,946
  • 3
  • 43
  • 82
1

Facets' Hash#rekey is also a worth mentioning.

Sample:

require 'facets/hash/rekey'
{ "id" => "123", "name" => "test" }.deep_rekey
=> {:id=>"123", :name=>"test"}

There is also a recursive version:

require 'facets/hash/deep_rekey'
{ "id" => "123", "name" => {"first" => "John", "last" => "Doe" } }.deep_rekey
=> {:id=>"123", :name=>{:first=>"John", :last=>"Doe"}}
Sergikon
  • 2,314
  • 1
  • 13
  • 3
0

Here's a little recursive function to do a deep symbolization of the keys:

def symbolize_keys(hash)
  Hash[hash.map{|k,v| v.is_a?(Hash) ? [k.to_sym, symbolize_keys(v)] : [k.to_sym, v] }]
end
Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38