【问题标题】:Rails converts empty arrays into nils in params of the requestRails 在请求的参数中将空数组转换为 nil
【发布时间】:2013-01-16 20:23:56
【问题描述】:

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

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

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

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

我的 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 情况下如何最好地处理这个问题?

【问题讨论】:

标签: ruby-on-rails json


【解决方案1】:

你可以自己重新解析参数,像这样:

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 方法以返回重新解析的参数(如果存在)。

【讨论】:

  • 我发现 this gist 正在使用 Rails 3.2.13。
  • @Grandpa 将重新解析的参数与原始参数合并不是更好吗? (例如 url 请求参数会丢失)
  • 请注意。重新定义params 会因params 特定的方法而崩溃,例如params.permit()
【解决方案2】:

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

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

然后在你的控制器中......

before_action :fix_empty_child_params, only: [:update]

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

我遇到了这个问题,在我的情况下,如果 POSTed 资源包含 child_ids: []child_ids: nil,我希望该更新意味着“删除所有孩子”。如果客户端不打算更新 child_ids 列表,则不应在 POST 正文中发送它,在这种情况下,params[:resource].include? attr 将是 false,并且请求参数将保持不变。

【讨论】:

  • @aceofspades 我认为你是对的!尽管如果客户发送PATCH 来删除与另一个资源的所有关系,例如{user: {child_ids:[]}},如果没有这个解决方案,这是否可行?
  • 否则,如果请求方法是PATCH,上面可以更新立即返回。遗憾的是,在PATCH 的情况下,很难确定参数是被发送为无效的还是不是由客户端发送的。
【解决方案3】:

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

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

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

【讨论】:

  • 这帮助我使用了一个多选元素,该元素使用隐藏字段来提交所选值,但如果没有选择,则没有。该技术让我想起了 check_box_tag 表单助手中内置的复选框技巧。
【解决方案4】:

我遇到了类似的问题。

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

所以理想情况下你的参数应该喜欢

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

所以我总是在我的请求中传递 ("") 而不是发送空数组以绕过深度修改过程。

【讨论】:

    【解决方案5】:

    经过大量搜索,我发现您从 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

    【讨论】:

    • 如果我使用的是旧版 Rails,您知道如何禁用它吗?
    【解决方案6】:

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

    如果您知道空数组的位置,您总是可以在前置过滤器中使用params[:...][:...] ||= []

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

    丑陋,但它会工作。

    【讨论】:

    • 这就是它最初的工作方式,我在 Backbone 中进行解析/字符串化。但是我认为在发布时最好不要使用部分字符串化的 JSON - 只发布一个大型 JSON 似乎很酷,就好像我正在与一个基于文档的存储 API 交谈一样。
    • Opps.. enter 提交此文本区域。目前,我只是按照github.com/rails/rails/pull/8862 中的讨论对 ActionDispatch::Request.deep_munge 进行了修补,直到它在 Rails stable 中推出,这可能是因为他们在 2 天前合并了 PR。
    猜你喜欢
    • 1970-01-01
    • 2022-09-24
    • 2013-12-07
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 1970-01-01
    • 2019-06-12
    • 2016-08-26
    相关资源
    最近更新 更多