Converting nested hash keys from CamelCase to snake_case in Ruby

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


You need to treat Array and Hash separately. And, if you're in Rails, you can use underscore instead of your homebrew to_snake_case. First a little helper to reduce the noise:

def underscore_key(k)
  k.to_s.underscore.to_sym
  # Or, if you're not in Rails:
  # to_snake_case(k.to_s).to_sym
end

If your Hashes will have keys that aren't Symbols or Strings then you can modify underscore_key appropriately.

If you have an Array, then you just want to recursively apply convert_hash_keys to each element of the Array; if you have a Hash, you want to fix the keys with underscore_key and apply convert_hash_keys to each of the values; if you have something else then you want to pass it through untouched:

def convert_hash_keys(value)
  case value
    when Array
      value.map { |v| convert_hash_keys(v) }
      # or `value.map(&method(:convert_hash_keys))`
    when Hash
      Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }]
    else
      value
   end
end

The accepted answer by 'mu is too short' has been converted into a gem, futurechimp's Plissken:

https://github.com/futurechimp/plissken/blob/master/lib/plissken/ext/hash/to_snake_keys.rb

This looks like it should work outside of Rails as the underscore functionality is included.


I use this short form:

hash.transform_keys(&:underscore)

And, as @Shanaka Kuruwita pointed out, to deeply transform all the nested hashes:

hash.deep_transform_keys(&:underscore)