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

时间:2022-11-26 21:59:33

I have a Backbone model in my app which is not a typical flat object, it's a large nested object and we store the nested parts in TEXT columns in a MySQL database.

我的应用中有一个主干模型它不是一个典型的平面对象,它是一个大的嵌套对象我们将嵌套的部分存储在MySQL数据库的文本列中。

I wanted to handle the JSON encoding/decoding in Rails API so that from outside it looks like you can POST/GET this one large nested JSON object even if parts of it are stored as stringified JSON text.

我想在Rails API中处理JSON编码/解码,这样从外部看起来就可以发布/获取这个大型嵌套的JSON对象,即使它的部分存储为stringified JSON文本。

However, I ran into an issue where Rails magically converts empty arrays to nil values. For example, if I POST this:

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

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

My Rails controller sees this:

我的Rails控制器看到这个:

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

And so my JSON data has been altered..

所以我的JSON数据被修改了。

Has anyone run into this issue before? Why would Rails be modifying my POST data?

以前有人遇到过这个问题吗?为什么Rails会修改我的POST数据?

UPDATE

更新

Here is where they do it:

以下是他们的做法:

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

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

And here is ~why they do it:

这就是他们为什么这样做的原因:

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

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

So now the question is, how to best deal with this in my nested JSON API situation?

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

7 个解决方案

#1


38  

After much searching, I discovered that you starting in Rails 4.1 you can skip the deep_munge "feature" completely using

经过大量搜索,我发现您可以从Rails 4.1开始,完全使用deep_munge“特性”就可以了

config.action_dispatch.perform_deep_munge = false

I could not find any documentation, but you can view the introduction of this option here: https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247

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

There is a possible security risk in doing so, documented here: https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI

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

#2


9  

Looks like this is a known, recently introduced issue: https://github.com/rails/rails/issues/8832

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

If you know where the empty array will be you could always params[:...][:...] ||= [] in a before filter.

如果你知道空的数组在哪里,你就可以用params[:]。[] ||= [] in a before filter。

Alternatively you could modify your BackBone model's to JSON method, explicitly stringifying the nested_json value using JSON.stringify() before it gets posted and manually parsing it back out using JSON.parse in a before_filter.

或者,您也可以将主干模型修改为JSON方法,使用JSON.stringify()显式地对nested_json值进行stringify(),然后将其发送出去,并使用JSON手工解析出来。before_filter解析。

Ugly, but it'll work.

丑,但是它会工作。

#3


7  

You can re-parse the parameters on your own, like this:

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

class ApiController
  before_filter :fix_json_params

  [...]

  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

This works by looking for requests with a JSON content-type, re-parsing the request body, and then intercepting the params method to return the re-parsed parameters if they exist.

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

#4


2  

I ran into similar issue.

我遇到了类似的问题。

Fixed it by sending empty string as part of the array.

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

So ideally your params should like

所以理想情况下,你的伴侣应该喜欢

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

So instead of sending empty array I always pass ("") in my request to bypass the deep munging process.

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

#5


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.

这里(我相信)一个合理的解决方案,不涉及重新解析原始请求主体。如果您的客户端发布表单数据,但在我的情况下,我发布的是JSON,那么这可能不起作用。

in application_controller.rb:

在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.

我遇到了这个问题,在我的情况下,如果一个已发布的资源包含child_id:[]或child_id: nil,我希望这个更新意味着“删除所有的子元素”。如果客户端不打算更新child_ids列表,那么它不应该被发送到POST主体中,在这种情况下params[:resource].include?attr将为false,请求参数将保持不变。

#6


1  

I ran into a similar issue and found out that passing an array with an empty string would be processed correctly by Rails, as mentioned above. If you encounter this while submitting a form, you might want to include an empty hidden field that matches the array param :

我遇到了一个类似的问题,发现使用一个空字符串的数组将被Rails正确处理,如上所述。如果您在提交表单时遇到此情况,您可能希望包含一个与数组param匹配的空隐藏字段:

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

When the actual param is empty the controller will always see an array with an empty string, thus keeping the submission stateless.

当实际的param为空时,控制器总是会看到一个带有空字符串的数组,从而保持提交无状态。

#7


-5  

Here's a way to work around this issue.

这里有一个解决这个问题的方法。

def fix_nils obj

  # fixes an issue where rails turns [] into nil in json data passed to params

  case obj
    when nil
      return []
    when Array
      return obj.collect { |x| nils_to_empty_arrays x }
    when Hash
      newobj = {}
      obj.each do |k,v|
        newobj[k] = nils_to_empty_arrays v
      end
      return newobj
    else
      return obj
  end

end

And then just do

然后就做

fixed_params = fix_nils params

which works as long as you don't have nils in your params on purpose.

只要你没有在你的帕姆斯的目标上有nils。

#1


38  

After much searching, I discovered that you starting in Rails 4.1 you can skip the deep_munge "feature" completely using

经过大量搜索,我发现您可以从Rails 4.1开始,完全使用deep_munge“特性”就可以了

config.action_dispatch.perform_deep_munge = false

I could not find any documentation, but you can view the introduction of this option here: https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247

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

There is a possible security risk in doing so, documented here: https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI

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

#2


9  

Looks like this is a known, recently introduced issue: https://github.com/rails/rails/issues/8832

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

If you know where the empty array will be you could always params[:...][:...] ||= [] in a before filter.

如果你知道空的数组在哪里,你就可以用params[:]。[] ||= [] in a before filter。

Alternatively you could modify your BackBone model's to JSON method, explicitly stringifying the nested_json value using JSON.stringify() before it gets posted and manually parsing it back out using JSON.parse in a before_filter.

或者,您也可以将主干模型修改为JSON方法,使用JSON.stringify()显式地对nested_json值进行stringify(),然后将其发送出去,并使用JSON手工解析出来。before_filter解析。

Ugly, but it'll work.

丑,但是它会工作。

#3


7  

You can re-parse the parameters on your own, like this:

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

class ApiController
  before_filter :fix_json_params

  [...]

  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

This works by looking for requests with a JSON content-type, re-parsing the request body, and then intercepting the params method to return the re-parsed parameters if they exist.

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

#4


2  

I ran into similar issue.

我遇到了类似的问题。

Fixed it by sending empty string as part of the array.

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

So ideally your params should like

所以理想情况下,你的伴侣应该喜欢

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

So instead of sending empty array I always pass ("") in my request to bypass the deep munging process.

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

#5


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.

这里(我相信)一个合理的解决方案,不涉及重新解析原始请求主体。如果您的客户端发布表单数据,但在我的情况下,我发布的是JSON,那么这可能不起作用。

in application_controller.rb:

在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.

我遇到了这个问题,在我的情况下,如果一个已发布的资源包含child_id:[]或child_id: nil,我希望这个更新意味着“删除所有的子元素”。如果客户端不打算更新child_ids列表,那么它不应该被发送到POST主体中,在这种情况下params[:resource].include?attr将为false,请求参数将保持不变。

#6


1  

I ran into a similar issue and found out that passing an array with an empty string would be processed correctly by Rails, as mentioned above. If you encounter this while submitting a form, you might want to include an empty hidden field that matches the array param :

我遇到了一个类似的问题,发现使用一个空字符串的数组将被Rails正确处理,如上所述。如果您在提交表单时遇到此情况,您可能希望包含一个与数组param匹配的空隐藏字段:

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

When the actual param is empty the controller will always see an array with an empty string, thus keeping the submission stateless.

当实际的param为空时,控制器总是会看到一个带有空字符串的数组,从而保持提交无状态。

#7


-5  

Here's a way to work around this issue.

这里有一个解决这个问题的方法。

def fix_nils obj

  # fixes an issue where rails turns [] into nil in json data passed to params

  case obj
    when nil
      return []
    when Array
      return obj.collect { |x| nils_to_empty_arrays x }
    when Hash
      newobj = {}
      obj.each do |k,v|
        newobj[k] = nils_to_empty_arrays v
      end
      return newobj
    else
      return obj
  end

end

And then just do

然后就做

fixed_params = fix_nils params

which works as long as you don't have nils in your params on purpose.

只要你没有在你的帕姆斯的目标上有nils。