264
votes

What's the (fastest/cleanest/straightforward) way to convert all keys in a hash from strings to symbols in Ruby?

This would be handy when parsing YAML.

my_hash = YAML.load_file('yml')

I'd like to be able to use:

my_hash[:key] 

Rather than:

my_hash['key']
30
hash.symbolize_keys and hash.deep_symbolize_keys do the job if you're using Rails. - Zaz
Josh if you would have put your comment into an answer, I would have voted you up. require 'rails';hash.deep_symbolize_keys works pretty well in irb or pry. :D - Douglas G. Allen

30 Answers

289
votes

In Ruby >= 2.5 (docs) you can use:

my_hash.transform_keys(&:to_sym)

Using older Ruby version? Here is a one-liner that will copy the hash into a new one with the keys symbolized:

my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}

With Rails you can use:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 
311
votes

Here's a better method, if you're using Rails:

params.symbolize_keys

The end.

If you're not, just rip off their code (it's also in the link):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
115
votes

For the specific case of YAML in Ruby, if the keys begin with ':', they will be automatically interned as symbols.

require 'yaml'
require 'pp'
yaml_str = "
connections:
  - host: host1.example.com
    port: 10000
  - host: host2.example.com
    port: 20000
"
yaml_sym = "
:connections:
  - :host: host1.example.com
    :port: 10000
  - :host: host2.example.com
    :port: 20000
"
pp yaml_str = YAML.load(yaml_str)
puts yaml_str.keys.first.class
pp yaml_sym = YAML.load(yaml_sym)
puts yaml_sym.keys.first.class

Output:

#  /opt/ruby-1.8.6-p287/bin/ruby ~/test.rb
{"connections"=>
  [{"port"=>10000, "host"=>"host1.example.com"},
   {"port"=>20000, "host"=>"host2.example.com"}]}
String
{:connections=>
  [{:port=>10000, :host=>"host1.example.com"},
   {:port=>20000, :host=>"host2.example.com"}]}
Symbol
64
votes

Even more terse:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
62
votes

if you're using Rails, it is much simpler - you can use a HashWithIndifferentAccess and access the keys both as String and as Symbols:

my_hash.with_indifferent_access 

see also:

http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html


Or you can use the awesome "Facets of Ruby" Gem, which contains a lot of extensions to Ruby Core and Standard Library classes.

  require 'facets'
  > {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
    =>  {:some=>"thing", :foo=>"bar}

see also: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash

56
votes

Since Ruby 2.5.0 you can use Hash#transform_keys or Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
32
votes

http://api.rubyonrails.org/classes/Hash.html#method-i-symbolize_keys

hash = { 'name' => 'Rob', 'age' => '28' }
hash.symbolize_keys
# => { name: "Rob", age: "28" }
27
votes

Here's a way to deep symbolize an object

def symbolize(obj)
    return obj.inject({}){|memo,(k,v)| memo[k.to_sym] =  symbolize(v); memo} if obj.is_a? Hash
    return obj.inject([]){|memo,v    | memo           << symbolize(v); memo} if obj.is_a? Array
    return obj
end
24
votes

If you are using json, and want to use it as a hash, in core Ruby you can do it:

json_obj = JSON.parse(json_str, symbolize_names: true)

symbolize_names: If set to true, returns symbols for the names (keys) in a JSON object. Otherwise strings are returned. Strings are the default.

Doc: Json#parse symbolize_names

21
votes

I really like the Mash gem.

you can do mash['key'], or mash[:key], or mash.key

13
votes

params.symbolize_keys will also work. This method turns hash keys into symbols and returns a new hash.

12
votes

In Rails you can use:

{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!

Converts to:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
11
votes

A modification to @igorsales answer

class Object
  def deep_symbolize_keys
    return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
    return self.inject([]){|memo,v    | memo           << v.deep_symbolize_keys; memo} if self.is_a? Array
    return self
  end
end
8
votes

So many answers here, but the one method rails function is hash.symbolize_keys

8
votes

This is my one liner for nested hashes

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
7
votes

In case the reason you need to do this is because your data originally came from JSON, you could skip any of this parsing by just passing in the :symbolize_names option upon ingesting JSON.

No Rails required and works with Ruby >1.9

JSON.parse(my_json, :symbolize_names => true)
5
votes

You could be lazy, and wrap it in a lambda:

my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }

my_lamb[:a] == my_hash['a'] #=> true

But this would only work for reading from the hash - not writing.

To do that, you could use Hash#merge

my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))

The init block will convert the keys one time on demand, though if you update the value for the string version of the key after accessing the symbol version, the symbol version won't be updated.

irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a]  # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a]  # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}

You could also have the init block not update the hash, which would protect you from that kind of error, but you'd still be vulnerable to the opposite - updating the symbol version wouldn't update the string version:

irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}

So the thing to be careful of with these is switching between the two key forms. Stick with one.

4
votes

Would something like the following work?

new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }

It'll copy the hash, but you won't care about that most of the time. There's probably a way to do it without copying all the data.

4
votes

a shorter one-liner fwiw:

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
3
votes

How about this:

my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))

# my_hash['key'] => "val"
# my_hash[:key]  => "val"
3
votes

This is for people who uses mruby and do not have any symbolize_keys method defined:

class Hash
  def symbolize_keys!
    self.keys.each do |k|
      if self[k].is_a? Hash
        self[k].symbolize_keys!
      end
      if k.is_a? String
        raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
        self[k.to_sym] = self[k]
        self.delete(k)
      end
    end
    return self
  end
end

The method:

  • symbolizes only keys that are String
  • if symbolize a string means to lose some informations (overwrite part of hash) raise a RuntimeError
  • symbolize also recursively contained hashes
  • return the symbolized hash
  • works in place!
2
votes

The array we want to change.

strings = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]

Make a new variable as an empty array so we can ".push" the symbols in.

symbols = [ ]

Here's where we define a method with a block.

strings.each {|x| symbols.push(x.intern)}

End of code.

So this is probably the most straightforward way to convert strings to symbols in your array(s) in Ruby. Make an array of strings then make a new variable and set the variable to an empty array. Then select each element in the first array you created with the ".each" method. Then use a block code to ".push" all of the elements in your new array and use ".intern or .to_sym" to convert all the elements to symbols.

Symbols are faster because they save more memory within your code and you can only use them once. Symbols are most commonly used for keys in hash which is great. I'm the not the best ruby programmer but this form of code helped me a lot.If anyone knows a better way please share and you can use this method for hash too!

2
votes

If you would like vanilla ruby solution and as me do not have access to ActiveSupport here is deep symbolize solution (very similar to previous ones)

    def deep_convert(element)
      return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
      return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
      element
    end
2
votes

Starting on Psych 3.0 you can add the symbolize_names: option

Psych.load("---\n foo: bar") # => {"foo"=>"bar"}

Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}

Note: if you have a lower Psych version than 3.0 symbolize_names: will be silently ignored.

My Ubuntu 18.04 includes it out of the box with ruby 2.5.1p57

1
votes
ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
 => {"aaa"=>1, "bbb"=>2} 
ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
 => {:aaa=>1, :bbb=>2}
1
votes

This is not exactly a one-liner, but it turns all string keys into symbols, also the nested ones:

def recursive_symbolize_keys(my_hash)
  case my_hash
  when Hash
    Hash[
      my_hash.map do |key, value|
        [ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
      end
    ]
  when Enumerable
    my_hash.map { |value| recursive_symbolize_keys(value) }
  else
    my_hash
  end
end
1
votes

I like this one-liner, when I'm not using Rails, because then I don't have to make a second hash and hold two sets of data while I'm processing it:

my_hash = { "a" => 1, "b" => "string", "c" => true }

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }

my_hash
=> {:a=>1, :b=>"string", :c=>true}

Hash#delete returns the value of the deleted key

1
votes

Facets' Hash#deep_rekey is also a good option, especially:

  • if you find use for other sugar from facets in your project,
  • if you prefer code readability over cryptical one-liners.

Sample:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
1
votes

In ruby I find this to be the most simple and easy to understand way to turn string keys in hashes to symbols :

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}

For each key in the hash we call delete on it which removes it from the hash (also delete returns the value associated with the key that was deleted) and we immediately set this equal to the symbolized key.

1
votes

Similar to previous solutions but written a bit differently.

  • This allows for a hash that is nested and/or has arrays.
  • Get conversion of keys to a string as a bonus.
  • Code does not mutate the hash been passed in.

    module HashUtils
      def symbolize_keys(hash)
        transformer_function = ->(key) { key.to_sym }
        transform_keys(hash, transformer_function)
      end
    
      def stringify_keys(hash)
        transformer_function = ->(key) { key.to_s }
        transform_keys(hash, transformer_function)
      end
    
      def transform_keys(obj, transformer_function)
        case obj
        when Array
          obj.map{|value| transform_keys(value, transformer_function)}
        when Hash
          obj.each_with_object({}) do |(key, value), hash|
            hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
          end
        else
          obj
        end
      end
    end