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

Published by Avdi Grimm

9 Comments

  1. 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).

    Reply
    • 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.

      Reply
  2. 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

    Reply
  3. using each_pair is faster than inject (in my tests on ruby 1.8.7 EE.

    Reply
  4. Thanks!

    Reply
  5. Thanks!

    Reply
  6. This doesn’t work for arrays inside the hash containing further hashes. The “inner” hashes are not symbolized.

    Reply
  7. This doesn’t work if value is an Array of Hashes.

    Updated version : https://gist.github.com/neektza/8585746

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *