tags:

views:

1008

answers:

6

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']
+2  A: 

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
+2  A: 

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
+4  A: 

If you want a one-liner,

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

will copy the hash into a new one with the keys symbolized.

Sarah Mei
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.
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
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
+6  A: 

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

params.to_options

The end.

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

Sai Emrys
A: 

I really like the Mash gem.

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

ykaganovich
+2  A: 

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
jrgm
That'll be very handy for future reference. Thank you.
Bryan M.