Rails在请求的参数中将空数组转换为nils

时间:2013-02-01 13:48:53

标签: ruby-on-rails json

我的应用程序中有一个Backbone模型,它不是一个典型的扁平对象,它是一个大型嵌套对象,我们将嵌套部分存储在MySQL数据库的TEXT列中。

我想在Rails API中处理JSON编码/解码,以便从外部看起来你可以POST / GET这个大型嵌套JSON对象,即使它的一部分存储为字符串化的JSON文本。

但是,我遇到了一个问题,Rails神奇地将空数组转换为nil值。例如,如果我发布这个:

{
  name: "foo",
  surname: "bar",
  nested_json: {
    complicated: []
  }
}

My Rails控制器看到了这个:

{
  :name => "foo",
  :surname => "bar",
  :nested_json => {
    :complicated => nil
  }
}

因此我的JSON数据已被更改..

之前是否有人遇到此问题?为什么Rails会修改我的POST数据?

更新

这是他们这样做的地方:

https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288

这是〜为什么他们这样做:

https://github.com/rails/rails/pull/8862

现在的问题是,如何在我的嵌套JSON API情况下最好地处理这个问题?

6 个答案:

答案 0 :(得分:41)

经过多次搜索,我发现你从Rails 4.1开始,你可以完全跳过deep_munge“功能”

config.action_dispatch.perform_deep_munge = false

我找不到任何文档,但您可以在此处查看此选项的介绍: https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247

这样做可能存在安全风险,请在此处进行说明:https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI

答案 1 :(得分:9)

看起来这是一个已知的,最近推出的问题:https://github.com/rails/rails/issues/8832

如果你知道空数组的位置,你可以在前一个过滤器中始终params[:...][:...] ||= []

或者,您可以将BackBone模型修改为JSON方法,在发布之前使用JSON.stringify()显式字符串化nested_json值,并使用before_filter中的JSON.parse手动解析它。

丑陋,但它会起作用。

答案 2 :(得分:8)

您可以自行重新解析参数,如下所示:

class ApiController
  before_filter :fix_json_params    # Rails 4 or earlier
  # before_action :fix_json_params  # Rails 5

  [...]

  protected

  def fix_json_params
    if request.content_type == "application/json"
      @reparsed_params = JSON.parse(request.body.string).with_indifferent_access
    end
  end

  private

  def params
    @reparsed_params || super
  end
end

这是通过查找具有JSON内容类型的请求,重新解析请求主体,然后拦截params方法以返回重新解析的参数(如果存在)来实现的。

答案 3 :(得分:2)

我遇到了类似的问题。

通过发送空字符串作为数组的一部分来修复它。

理想情况下,你的参数应该是

{
  name: "foo",
  surname: "bar",
  nested_json: {
    complicated: [""]
  }
}

因此,在我的请求中,我总是传递(“”)而不是发送空数组,以绕过深层修改过程。

答案 4 :(得分:2)

Here's (I believe) a reasonable solution that does not involve re-parsing the raw request body. This might not work if your client is POSTing form data but in my case I'm POSTing JSON.

in application_controller.rb:

  # replace nil child params with empty list so updates occur correctly
  def fix_empty_child_params resource, attrs
    attrs.each do |attr|
      params[resource][attr] = [] if params[resource].include? attr and params[resource][attr].nil?
    end
  end

Then in your controller....

before_action :fix_empty_child_params, only: [:update]

def fix_empty_child_params
  super :user, [:child_ids, :foobar_ids]
end

I ran into this and in my situation, if a POSTed resource contains either child_ids: [] or child_ids: nil I want that update to mean "remove all children." If the client intends not to update the child_ids list then it should not be sent in the POST body, in which case params[:resource].include? attr will be false and the request params will be unaltered.

答案 5 :(得分:1)

我遇到了类似的问题,发现传递一个带有空字符串的数组将由Rails正确处理,如上所述。 如果您在提交表单时遇到此问题,则可能需要包含与数组参数匹配的空隐藏字段:

<input type="hidden" name="model[attribute_ids][]"/>

当实际参数为空时,控制器将始终看到一个带有空字符串的数组,从而使提交无状态。