Given a YAML stream that looks like this:
--- drinks: martini: garnish: olive gibson: garnish: onion
The Ruby-fied version looks like this:
{"drinks"=>{"gibson"=>{"garnish"=>"onion"}, "martini"=>{"garnish"=>"olive"}}}
But we don’t like string keys, we like symbol keys. A number of libraries exist to extend Hash with interchangeable string- or key-based indexing. But adding a library dependency seems like overkill for symbolizing some keys. Here’s a quick recursive string-to-symbol key converter:
def symbolize_keys(hash) hash.inject({}){|result, (key, value)| new_key = case key when String then key.to_sym else key end new_value = case value when Hash then symbolize_keys(value) else value end result[new_key] = new_value result } end
Note the block parameters to Hash#inject
.
hash.inject({}){|result, (key, value)|
The block arguments would normally be a hash (the accumulator) and a two-element array of [key, value]. Putting parentheses around the second two parameters causes the second argument to be “splatted” into its component parts and assigned to key
and value
separately.
Let’s take a look at the output.
puts "Symbolized hash:" puts symbolize_keys(YAML.load(yaml)).inspect
Symbolized hash: {:drinks=>{:martini=>{:garnish=>"olive"}, :gibson=>{:garnish=>"onion"}}}
BTW you can also use symbols in YAML, just do something like that:
—
:key: value
About your example, I personally prefer to create SymbolizedKeysMixin, include it to the Hash class and use key.respond_to?(:symbolize_keys) rather than testing class of each key. So the only thing for supporting another classes will be e. g. Mash.send(:include, SymbolizedKeysMixin).
Yeah, I know about the symbol syntax in YAML. But a lot of YAML formats don't use it, presumably because the string syntax is more pleasant to read, and perhaps because insisting on adding the file's symbols to the system string table is arguably presumptuous. Or maybe just because it's easy to forget to type that leading colon.
A running theme in my work is solutions that don't require extending core classes, but there are certainly advantages to the module inclusion approach.
I also like to have symbolize_keys be a method on Hash rather than passing the hash to symbolize_keys, otherwise it feels like Python 🙂
Here it is with that and ternaries, which is a little more compact but I think still readable.
http://gist.github.com/151324
using each_pair is faster than inject (in my tests on ruby 1.8.7 EE.
Thanks!
Thanks!
This doesn’t work for arrays inside the hash containing further hashes. The “inner” hashes are not symbolized.
Modified to work with nested arrays:
https://gist.github.com/ncri/5661189
This doesn’t work if value is an Array of Hashes.
Updated version : https://gist.github.com/neektza/8585746