53

如您所知,JSON 命名约定提倡使用 camelCase,Rails 提倡使用 snake_case 作为参数名称。

在rails控制器中将所有请求的参数转换为snake_case的最佳方法是什么?

由此:

{
  ...
  "firstName": "John",
  "lastName": "Smith",
  "moreInfo":
  {
    "mealType": 2,
    "mealSize": 4,
    ...
  }
}

对此:

{
  ...
  "first_name": "John",
  "last_name": "Smith",
  "more_info":
  {
    "meal_type": 2,
    "meal_size": 4,
    ...
  }
}
4

14 回答 14

87

完成以下步骤后,camelCase通过 JSON 请求提交的参数名称将更改为snake_case.

例如,一个名为的 JSON 请求参数passwordConfirmation将在控制器中被访问为params[:password_confirmation]

在 处创建一个初始化程序config/initializers/json_param_key_transform.rb。该文件将仅更改 JSON 请求的参数解析行为(JSON 请求必须具有请求标头Content-Type: application/json)。

找到您的 Rails 版本并选择下面的相应部分(在 中找到您的 Rails 版本Gemfile.lock):

对于 Rails 5 和 6

对于 Rails 5 和 6,要将驼峰式参数键转换为蛇形,请将其放入初始化程序中:

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

对于 Rails 4.2(可能还有更早的版本)

对于 Rails 4.2(可能还有更早的版本),要将驼峰式参数键转换为蛇形,请将其放入初始化程序中:

# 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
  }
)

所有 Rails 版本的最后一步

重启rails server

于 2015-05-31T13:21:27.610 回答
16

在rails控制台中使用camelCase到snake_case的示例

2.3.1 :001 > params = ActionController::Parameters.new({"firstName"=>"john", "lastName"=>"doe", "email"=>"john@doe.com"})
=> <ActionController::Parameters {"firstName"=>"john", "lastName"=>"doe", "email"=>"john@doe.com"} permitted: false>

2.3.1 :002 > params.transform_keys(&:underscore)
=> <ActionController::Parameters {"first_name"=>"john", "last_name"=>"doe", "email"=>"john@doe.com"} permitted: false>

资源:

http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-transform_keys http://apidock.com/rails/String/underscore

更新

如果你有嵌套属性Rails 6,你可以这样做:

ActionController::Parameters 转换为哈希,然后进行深度转换:

params.permit!.to_h.deep_transform_keys { |key| key.to_s.underscore } params.permit!.to_h.deep_transform_values { |value| value.to_s.underscore }

请参见:

http://apidock.com/rails/v6.0.0/Hash/deep_transform_values http://apidock.com/rails/v6.0.0/Hash/deep_transform_keys

于 2016-10-17T18:54:00.360 回答
12

Rails 6.1中将被添加deep_transform_keysActionController::Parameters因此它使您能够使其变得简单:

class ApplicationController < ActionController::Base
  before_action :underscore_params!

  private

  def underscore_params!
    params.deep_transform_keys!(&:underscore)
  end
end

编辑

目前,您可以按如下方式向后移植:

module DeepTransformKeys
  def deep_transform_keys!(&block)
    @parameters.deep_transform_keys!(&block)
    self
  end
end

ActionController::Parameters.include(DeepTransformKeys)
于 2019-09-27T08:23:37.833 回答
8

将 Sebastian Hoitz 的答案与这个 gist合并,我可以让它在 rails 4.2、强参数和使用wrap_parameters标记方法包装的参数上工作。

我无法使用 a 使其工作before_filter,可能是因为参数包装是在过滤之前完成的。

config/initializers/wrap_parameters.rb

# Convert json parameters, sent from Javascript UI, from camelCase to snake_case.
# This bridges the gap between javascript and ruby naming conventions.
module ActionController
  module ParamsNormalizer
    extend ActiveSupport::Concern

    def process_action(*args)
      deep_underscore_params!(request.parameters)
      super
    end

    private
      def deep_underscore_params!(val)
        case val
        when Array
          val.map {|v| deep_underscore_params! v }
        when Hash
          val.keys.each do |k, v = val[k]|
            val.delete k
            val[k.underscore] = deep_underscore_params!(v)
          end
          val
        else
          val
        end
      end
  end
end

# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
  wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
  # Include the above defined concern
  include ::ActionController::ParamsNormalizer
end
于 2015-01-17T04:06:22.077 回答
8

ActiveSupport 已经提供了一个 String#snakecase 方法。您所要做的就是安装一个过滤器,该过滤器通过 params 哈希进行深度迭代并将键替换为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
于 2013-07-18T23:57:55.317 回答
6

Rails 5的解决方案

before_action :underscore_params!

def underscore_params!
  underscore_hash = -> (hash) do
    hash.transform_keys!(&:underscore)
    hash.each do |key, value|
      if value.is_a?(ActionController::Parameters)
        underscore_hash.call(value)
      elsif value.is_a?(Array)
        value.each do |item|
          next unless item.is_a?(ActionController::Parameters)
          underscore_hash.call(item)
        end
      end
    end
  end
  underscore_hash.call(params)
end
于 2016-10-26T20:03:36.670 回答
5

我不能在这里直接使用其他建议,但它让我走上了正轨。

在 Rails 5.2 中,使用版本化 API,因此无法为整个应用程序更改它。我创建了这个问题,然后将其包含在我的新 api 版本模块的基本控制器中。

module UnderscoreizeParams
  extend ActiveSupport::Concern

  def process_action(*args)
    request.parameters.deep_transform_keys!(&:underscore)
    super
  end
end

然后在我的 API V3 BaseController

class V3::BaseController
  include UnderscoreizeParams
end

请享用。

于 2019-07-13T21:39:35.117 回答
4

Another rails 5.1 solution that piggy backs off of the Sebastian Hoitz solution above. To clarify why we need to do this: in R5.1 deep_transform_keys! is no longer a method available to us, since params are no longer inheriting from HashWithIndifferentAccess. And overcomes the issue mentioned by Eliot Sykes where the initializer only works for application/json mime types. It does add overhead to all the requests though. (I'd love to see some initializers for ActionDispatch::Request.parameter_parsers[:multipart_form]) though, since the initializer is a better place to be doing this, IMO.

before_action :normalize_key!

 def normalize_keys!(val = params)
  if val.class == Array
    val.map { |v| normalize_keys! v }
  else
    if val.respond_to?(:keys)
      val.keys.each do |k|
        current_key_value = val[k]
        val.delete k
        val[k.to_s.underscore] = normalize_keys!(current_key_value)
      end
    end
    val
  end
  val
end
于 2017-12-30T02:29:55.113 回答
2

我想使用 Chris Healds 版本,但是因为我使用的是 Rails 4,所以我启用了 strong_parameters,所以我不得不对其进行一些更改。

这是我想出的版本:

before_filter :deep_underscore_params!


def deep_underscore_params!(val = request.parameters)
  case val
  when Array
    val.map { |v| deep_underscore_params!(v) }
  when Hash
    val.keys.each do |k, v = val[k]|
      val.delete k
      val[k.underscore] = deep_underscore_params!(v)
    end

    params = val
  else
    val
  end
end
于 2014-11-28T16:32:04.307 回答
2

我们正在将 Rails API JSON 密钥从 snake_case 转换为 camelCase。我们必须逐步进行转换,即一些API 使用snake_case,而其他API 改为使用camelCase。

我们的解决方案是我们

  • 创建方法ActionController::Parameters#deep_snakeize
  • 创建方法ApplicationController#snakeize_params
  • before_action :snakeize_params仅针对使用 camelCase 键处理传入请求的控制器操作设置

您可以尝试vochicong/rails-json-api以获得完整的 Rails 应用程序示例。

# File: config/initializers/params_snakeizer.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case
module ActionController
  # Modified from action_controller/metal/strong_parameters.rb
  class Parameters
    def deep_snakeize!
      @parameters.deep_transform_keys!(&:underscore)
      self
    end
  end
end

# File: app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  protected

    # Snakeize JSON API request params
    def snakeize_params
      params.deep_snakeize!
    end
end

class UsersController < ApplicationController
  before_action :snakeize_params, only: [:create]

  # POST /users
  def create
    @user = User.new(user_params)

    if @user.save
      render :show, status: :created, location: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end
end
于 2017-12-21T14:52:06.347 回答
0

您可以创建一个在任何控制器调用之前运行的过滤器,并对其应用以下说明:

# 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)

您必须考虑到上述过程会在每个 Rails 调用上产生少量开销。

如果只是为了适应约定,我不相信这是必要的。

于 2013-06-21T17:19:04.073 回答
0

tlewin 的回答在 Rails 3 中对我不起作用。似乎 params' = 运算符使未来的操作无效。很奇怪。无论如何,以下对我有用,因为它只使用 []= 和删除运算符:

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
于 2013-07-18T23:47:43.720 回答
0

你可以试试这个:

class ApplicationController < ActionController::API
  include ControllerHelper
  before_action :deep_underscore_params!

  def deep_underscore_params!(app_params = params)
    app_params.transform_keys!(&:underscore)
    app_params.each do |key, value|
      deep_underscore_params!(value) if value.instance_of?(ActionController::Parameters)
    end
    app_params.reject! { |k, v| v.blank? }
  end
end
于 2018-10-18T11:17:55.863 回答
0

根据上面 Eliot Sykes 的回答,我认为我们可以在 Rails5 案例中做得更好。我不喜欢完全覆盖该函数,因为该代码可能会更改。所以我建议使用函数组合:

# 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)
    }
)
于 2019-07-12T09:27:38.863 回答