How do I convert a Ruby hash so that all of its keys are symbols?

Solution 1:

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"}}

Solution 2:

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.

Solution 3:

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

Solution 4:

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)

Solution 5:

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