Hash Transforms in Ruby

I often find myself wanting to perform some kind of transform on a Ruby Hash which results in another Hash. For instance, converting all keys to strings. Ruby’s built-in Hash methods make this a little inconvenient, because all the standard transforms (#map(), #select(), #reject(), etc…) return arrays of pairs instead of hashes:

h = { :foo => 1, :bar => {:baz => 3}, :buz => 4 }
h.map{|k,v| [k.to_s, v]}        # => [["foo", 1], ["bar", {:baz=>3}], ["buz", 4]]

So I wrote a generalized hash-transform utility method. It takes a Hash and a block and returns a new Hash. The block receives each key/value pair from the original hash, along with a new Hash to populate:

def transform_hash(original, options={}, &block)
  original.inject({}){|result, (key,value)|
    value = if (options[:deep] && Hash === value) 
              transform_hash(value, options, &block)
            else 
              value
            end
    block.call(result,key,value)
    result
  }
end

Here are some examples of use:

# Convert keys to strings
def stringify_keys(hash)
  transform_hash(hash) {|hash, key, value|       
    hash[key.to_s] = value
  }
end
stringify_keys(h)               # => {"foo"=>1, "buz"=>4, "bar"=>{:baz=>3}}

# Convert keys to strings, recursively
def deep_stringify_keys(hash)
  transform_hash(hash, :deep => true) {|hash, key, value|
    hash[key.to_s] = value
  }                               
end
deep_stringify_keys(h)          # => {"foo"=>1, "buz"=>4, "bar"=>{"baz"=>3}}

# Select a subset of entries
def slice_hash(hash, *keys)
  transform_hash(hash, :deep => true) {|hash, key, value|
    hash[key] = value if keys.include?(key)
  }
end
slice_hash(h, :foo, :buz)       # => {:foo=>1, :buz=>4}

Obviously any of these methods could have been written directly in terms of regular inject(), but transform_hash() lets you forget about the housekeeping required by inject() and focus on the task at hand. Rails users will probably also note that at least some of these tasks can also be accomplished using ActiveSupport extensions to Hash. transform_hash() is intended for situations where ActiveSupport may not be available, or does not provide exactly the transform you need.

The code is available as a gist, if you find it useful let me know.