Converting nested hash keys from CamelCase to snake_case in Ruby

Andrew Stewart picture Andrew Stewart · Jan 3, 2012 · Viewed 26.1k times · Source

I'm trying to build an API wrapper gem, and having issues with converting hash keys to a more Rubyish format from the JSON the API returns.

The JSON contains multiple layers of nesting, both Hashes and Arrays. What I want to do is to recursively convert all keys to snake_case for easier use.

Here's what I've got so far:

def convert_hash_keys(value)
  return value if (not value.is_a?(Array) and not value.is_a?(Hash))
  result = value.inject({}) do |new, (key, value)|
    new[to_snake_case(key.to_s).to_sym] = convert_hash_keys(value)
    new
  end
  result
end

The above calls this method to convert strings to snake_case:

def to_snake_case(string)
  string.gsub(/::/, '/').
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
  gsub(/([a-z\d])([A-Z])/,'\1_\2').
  tr("-", "_").
  downcase
end

Ideally, the result would be similar to the following:

hash = {:HashKey => {:NestedHashKey => [{:Key => "value"}]}}

convert_hash_keys(hash)
# => {:hash_key => {:nested_hash_key => [{:key => "value"}]}}

I'm getting the recursion wrong, and every version of this sort of solution I've tried either doesn't convert symbols beyond the first level, or goes overboard and tries to convert the entire hash, including values.

Trying to solve all this in a helper class, rather than modifying the actual Hash and String functions, if possible.

Thank you in advance.

Answer

Hubert Olender picture Hubert Olender · Oct 17, 2016

If you use Rails:

Example with hash: camelCase to snake_case:

hash = { camelCase: 'value1', changeMe: 'value2' }

hash.transform_keys { |key| key.to_s.underscore }
# => { "camel_case" => "value1", "change_me" => "value2" }

source: http://apidock.com/rails/v4.0.2/Hash/transform_keys

For nested attributes use deep_transform_keys instead of transform_keys, example:

hash = { camelCase: 'value1', changeMe: { hereToo: { andMe: 'thanks' } } }

hash.deep_transform_keys { |key| key.to_s.underscore }
# => {"camel_case"=>"value1", "change_me"=>{"here_too"=>{"and_me"=>"thanks"}}}

source: http://apidock.com/rails/v4.2.7/Hash/deep_transform_keys