64

我的应用程序中有一个 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 情况下最好地处理这个问题?

4

6 回答 6

45

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

于 2014-08-21T14:20:28.330 回答
11

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

如果你知道空数组在哪里,你总是可以params[:...][:...] ||= []在前置过滤器中。

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

丑陋,但它会工作。

于 2013-02-01T14:18:12.893 回答
9

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

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

于 2013-02-26T21:30:25.343 回答
3

我遇到了类似的问题。

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

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

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

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

于 2015-06-15T11:58:16.597 回答
2

这是(我相信)一个不涉及重新解析原始请求正文的合理解决方案。如果您的客户端正在发布表单数据,这可能不起作用,但在我的情况下,我正在发布 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? attrfalse请求参数将保持不变。

于 2016-07-11T21:05:38.257 回答
1

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

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

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

于 2016-03-09T12:51:12.790 回答