As you already know, JSON naming convention advocates the use of camelSpace and the Rails advocates the use of snake_case for parameter names.
What is the best way t
ActiveSupport already provides a String#snakecase method. All you have to do is install a filter that does a deep iteration through the params hash and replaces the keys with key.snakecase
.
before_filter :deep_snake_case_params!
def deep_snake_case_params!(val = params)
case val
when Array
val.map {|v| deep_snake_case_params! v }
when Hash
val.keys.each do |k, v = val[k]|
val.delete k
val[k.snakecase] = deep_snake_case_params!(v)
end
val
else
val
end
end
When you've completed the steps below, camelCase
param names submitted via JSON requests will be changed to snake_case
.
For example, a JSON request param named passwordConfirmation
would be accessed in a controller as params[:password_confirmation]
Create an initializer at config/initializers/json_param_key_transform.rb
. This file is going to change the parameter parsing behaviour for JSON requests only (JSON requests must have the request header Content-Type: application/json
).
Find your Rails version and choose the appropriate section below (find your Rails version in Gemfile.lock
):
For Rails 5 and 6, to convert camel-case param keys to snake-case, put this in the initializer:
# File: config/initializers/json_param_key_transform.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case:
ActionDispatch::Request.parameter_parsers[:json] = lambda { |raw_post|
# Modified from action_dispatch/http/parameters.rb
data = ActiveSupport::JSON.decode(raw_post)
# Transform camelCase param keys to snake_case
if data.is_a?(Array)
data.map { |item| item.deep_transform_keys!(&:underscore) }
else
data.deep_transform_keys!(&:underscore)
end
# Return data
data.is_a?(Hash) ? data : { '_json': data }
}
For Rails 4.2 (and maybe earlier versions), to convert camel-case param keys to snake-case, put this in the initializer:
# File: config/initializers/json_param_key_transform.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case:
Rails.application.config.middleware.swap(
::ActionDispatch::ParamsParser, ::ActionDispatch::ParamsParser,
::Mime::JSON => Proc.new { |raw_post|
# Borrowed from action_dispatch/middleware/params_parser.rb except for
# data.deep_transform_keys!(&:underscore) :
data = ::ActiveSupport::JSON.decode(raw_post)
data = {:_json => data} unless data.is_a?(::Hash)
data = ::ActionDispatch::Request::Utils.deep_munge(data)
# Transform camelCase param keys to snake_case:
data.deep_transform_keys!(&:underscore)
data.with_indifferent_access
}
)
Restart rails server
.
You can create a filter that runs before any controller call and apply the following instructions to it:
# transform camel case string into snake case
snake_string = Proc.new {|s| s.gsub(/([a-z])([A-Z])/) {|t| "#{$1}_#{$2.downcase}"}}
# transform all hash keys into snake case
snake_hash = Proc.new do |hash|
hash.inject({}) do |memo, item|
key, value = item
key = case key
when String
snake_string.call(key)
when Symbol
snake_string.call(key.to_s).to_sym
else
key
end
memo[key] = value.instance_of?(Hash) ? snake_hash.call(value) : value
memo
end
end
params = snake_hash.call(params)
You must have to consider the above procedure will impose a small overhead on every Rails call.
I am not convinced this is necessary, if it is just to fit in a convention.
Riffing on Eliot Sykes's answer above, I think we can do a bit better in the Rails5 case. I don't love overwriting that function entirely, since that code could change. So instead I suggest using function composition:
# File: config/initializers/json_param_key_transform.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case:
ActionDispatch::Request.parameter_parsers[:json] = (
# Compose the original parser with a transformation
ActionDispatch::Request.parameter_parsers[:json] >>
# Transform camelCase param keys to snake_case
->(data) {
data.deep_transform_keys(&:underscore)
}
)
In Rails 6.1 will be added deep_transform_keys
to ActionController::Parameters
so it enables you to make it as simple as:
class ApplicationController < ActionController::Base
before_action :underscore_params!
private
def underscore_params!
params.deep_transform_keys!(&:underscore)
end
end
Edit
At the moment you can backport as follows:
module DeepTransformKeys
def deep_transform_keys!(&block)
@parameters.deep_transform_keys!(&block)
self
end
end
ActionController::Parameters.include(DeepTransformKeys)
tlewin's answer didn't work for me in Rails 3. it seems the params' = operator renders future operations on it void. very strange. anyways the following works for me, as it only uses the []= and delete operators:
before_filter :underscore_param_keys
def underscore_param_keys
snake_hash = ->(hash) {
# copying the hash for iteration so we are not altering and iterating over the same object
hash.to_a.each do |key, value|
hash.delete key
hash[key.to_s.underscore] = value
snake_hash.call(value) if value.is_a? Hash
value.each { |item| snake_hash.call(item) if item.is_a? Hash } if value.is_a? Array
end
}
snake_hash.call(params)
end