288

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']
Max
  • 21,123
  • 5
  • 49
  • 71
Bryan M.
  • 17,142
  • 8
  • 46
  • 60

31 Answers31

344

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 
mattes
  • 8,936
  • 5
  • 48
  • 73
Sarah Mei
  • 18,154
  • 5
  • 45
  • 45
  • That looks like it should work to me, but: my_hash = {'foo' => 'bar', 'fiz' => 'buzz'} my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo} my_hash[:foo] # => nil my_hash['foo'] # => "bar" It's late and I'm tired. My brain might be missing some part of the puzzle, or the finer points of using inject(). – Bryan M. Apr 29 '09 at 02:55
  • 5
    Ah, sorry for being unclear - inject doesn't modify the caller. You need to do my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo} – Sarah Mei Apr 29 '09 at 19:06
  • 4
    That's exactly what I was looking for. I modified it a bit and added some lines to even create symbols in nestled hashes. Have a look here, if you're interested: http://www.any-where.de/blog/ruby-hash-convert-string-keys-to-symbols/ – Matt Aug 19 '09 at 14:57
  • 38
    In Ruby 1.9 you can use `each_with_object` like so: `my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}` – sgtFloyd Dec 28 '11 at 23:41
  • 10
    this doesn't handle recursive hashes... Find for a one-off but not for DRY. – baash05 Mar 26 '13 at 02:21
  • 8
    @BryanM. I've come into this discussion very late :-) but you can also use the `.tap` method to remove the need to pass `memo` at the end. I've created a cleaned up version of all solutions (recursive ones as well) https://gist.github.com/Integralist/9503099 – Integralist Mar 12 '14 at 09:00
  • Now with rails 4.1 and 4.2 is usefull to convert session hashes than are converted automatically – Albert Català Feb 25 '16 at 17:12
  • @baash05: here's a solution that hashes recursively: ``` def recursively_memoize obj if obj.is_a?(Hash) memoized_obj ||= {} obj.each do |k, v| memoized_obj[k.to_sym] = recursively_memoize(v) end return memoized_obj end obj end ``` – calcsam Aug 08 '16 at 21:09
  • i'm doing the other way, instead of symbolized key, i need quoted memo[k.to_s] but what about if nested hash? – aldrien.h Aug 22 '18 at 08:42
  • @SarahMei Your code examples seem ready to use but the 1st and 3rd need either a `my_hash = …` before or the `!` for the destructive variants to be really cut&paste ready. Could you add that? – tanius May 05 '20 at 21:57
319

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
JellicleCat
  • 28,480
  • 24
  • 109
  • 162
Sai
  • 6,919
  • 6
  • 42
  • 54
  • The link is broken, but I can confirm that this works in rails 3.0.3 – Matthew Rathbone Jan 28 '11 at 16:06
  • 6
    [to_options](http://apidock.com/rails/ActiveSupport/CoreExtensions/Hash/Keys/to_options) is an alias for [sybolize_keys](http://apidock.com/rails/ActiveSupport/CoreExtensions/Hash/Keys/symbolize_keys). – ma11hew28 Feb 09 '11 at 02:13
  • 48
    Will not symbolize nested hashes. – oma Mar 02 '11 at 12:08
  • 3
    I switched the link to `symbolize_keys` with the new & working (Rails 3) URL. I originally just fixed the URL for `to_options`, but there's zero documentation at that link. `symbolize_keys` actually has a description, so I used that instead. – Craig Walker Oct 24 '11 at 20:54
  • 23
    [deep_symbolize_keys!](http://api.rubyonrails.org/classes/Hash.html#method-i-deep_symbolize_keys-21). Works on rails 2+ – mastaBlasta Jul 25 '13 at 19:34
  • 15
    For those curious how to do the reverse, `hash.stringify_keys` works. – Nick Aug 07 '13 at 19:01
  • 3
    `my_hash = my_hash.deep_symbolize_keys` worked for me on Rails 3. Thanks @mastaBlasta! – Leo Apr 16 '14 at 12:57
  • 3
    [You can load the ActiveSupport core extensions in any Ruby project](http://edgeguides.rubyonrails.org/active_support_core_extensions.html), not just rails: `require 'active_support/core_ext/hash'` – hololeap Jun 21 '14 at 17:41
  • This also seem to be the fastest approach. – G. Allen Morris III Sep 03 '14 at 22:02
116

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
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
jrgm
  • 1,161
  • 1
  • 7
  • 3
  • 17
    Sweet! Is there a way to set `YAML#load_file` to default all keys to symbols instead of strings w/o having to begin every key with a colon? – ma11hew28 Feb 09 '11 at 02:31
68

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

Tilo
  • 33,354
  • 5
  • 79
  • 106
  • 2
    Actually that does the opposite. It converts from symbol to a string. To convert to a symbol use my_hash.symbolize_keys – Espen Jul 27 '14 at 12:15
  • #symbolize_keys only works in Rails - not in plain Ruby / irb. Also note that #symbolize_keys does not work on deeply nested hashes. – Tilo Jul 27 '14 at 17:11
65

Even more terse:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Michael Barton
  • 8,868
  • 9
  • 35
  • 43
58

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}
Sagar Pandya
  • 9,323
  • 2
  • 24
  • 35
32

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

hash = { 'name' => 'Rob', 'age' => '28' }
hash.symbolize_keys
# => { name: "Rob", age: "28" }
Ery
  • 501
  • 5
  • 5
30

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

Mark
  • 5,994
  • 5
  • 42
  • 55
  • `symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)` is a pretty DRY (but inefficient) way to quickly get a hash with nested keys as symbols if coming from a YAML file. – Cameron Gagnon Jun 19 '20 at 01:04
28

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
igorsales
  • 612
  • 6
  • 8
21

I really like the Mash gem.

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

ykaganovich
  • 14,736
  • 8
  • 59
  • 96
  • 2
    That's a very cool gem! Makes working with hashes very comfortable. Thanks for this! – asaaki Oct 20 '11 at 22:53
  • So beautifully simple. New development on this project is being continued in Hashie (https://github.com/intridea/hashie) but it still works pretty much the same way: https://github.com/intridea/hashie#keyconversion – jpalmieri Aug 19 '15 at 02:47
14

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
Tony
  • 18,776
  • 31
  • 129
  • 193
  • 1
    It would be helpful if you included why you were modifying the object for people scanning through answers. – Dbz Feb 22 '19 at 00:18
13

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

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Jae Cho
  • 499
  • 5
  • 11
13

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"}}}
mattes
  • 8,936
  • 5
  • 48
  • 73
JayJay
  • 746
  • 1
  • 9
  • 21
  • 3
    `deep_symbolize_keys` is added in Rails' [Hash](http://api.rubyonrails.org/classes/Hash.html#method-i-deep_symbolize_keys) extension, but it's not part of Ruby core. – Ryenski Jan 26 '18 at 02:07
8

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

shakirthow
  • 1,356
  • 14
  • 15
8

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
Nick Dobson
  • 81
  • 1
  • 1
  • FYI, only works for Rails. In which case, HashWithIndifferentAccess may be a better alternative. – Adam Grant Jul 16 '17 at 22:50
  • no it doesn't work only for rails. I works in plain ruby (irb) too. But it is only 2 levels deep. nested nested hashes don't get symbolized. – thiebo Nov 07 '22 at 20:10
7

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)
Adam Grant
  • 12,477
  • 10
  • 58
  • 65
5

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.

rampion
  • 87,131
  • 49
  • 199
  • 315
4

a shorter one-liner fwiw:

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
sensadrome
  • 472
  • 2
  • 7
4

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.

ChrisInEdmonton
  • 4,470
  • 5
  • 33
  • 48
3

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!
Matteo Ragni
  • 2,837
  • 1
  • 20
  • 34
  • 1
    There's a typo in your method, you forgot the `!` in `symbolize_keys`. Otherwise works fine. – Ka Mok Sep 07 '17 at 23:44
3

How about this:

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

# my_hash['key'] => "val"
# my_hash[:key]  => "val"
Fredrik Boström
  • 1,499
  • 1
  • 19
  • 22
2

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!

Community
  • 1
  • 1
2

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
Haris Krajina
  • 14,824
  • 12
  • 64
  • 81
2

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

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
Cjoerg
  • 1,271
  • 3
  • 21
  • 63
1

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

nevets138
  • 80
  • 8
1

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
Sergikon
  • 2,314
  • 1
  • 13
  • 3
1

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

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
    
jham
  • 76
  • 3
1
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}
Vlad Khomich
  • 5,820
  • 1
  • 27
  • 39
-1

symbolize_keys recursively for any hash:

class Hash
  def symbolize_keys
    self.is_a?(Hash) ? Hash[ self.map { |k,v| [k.respond_to?(:to_sym) ? k.to_sym : k, v.is_a?(Hash) ? v.symbolize_keys : v] } ] : self
  end
end
Jakub Zak
  • 1,212
  • 6
  • 32
  • 53