我的应用程序中有一个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数据?
UPDATE
这是他们这样做的地方:
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288
这是〜为什么他们这样做:
https://github.com/rails/rails/pull/8862
所以现在的问题是,如何在我的嵌套JSON API情况下最好地处理这个问题?
经过多次搜索,我发现你从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
看起来这是一个已知的,最近引入的问题:https://github.com/rails/rails/issues/8832
如果你知道空数组的位置,你可以在之前的过滤器中始终使用params[:...][:...] ||= []
。
或者,您可以将BackBone模型修改为JSON方法,在发布之前使用JSON.stringify()
显式字符串化nested_json值,并使用before_filter中的JSON.parse
手动解析它。
丑陋,但它会起作用。
您可以自己重新解析参数,如下所示:
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
方法以返回重新解析的参数(如果它们存在)来工作。
我遇到了类似的问题。
通过发送空字符串作为数组的一部分来修复它。
所以理想情况下你的params应该喜欢
{
name: "foo",
surname: "bar",
nested_json: {
complicated: [""]
}
}
所以不是发送空数组,而是总是在我的请求中传递(“”)以绕过深度整理过程。
这是(我相信)一个合理的解决方案,不涉及重新解析原始请求体。如果您的客户端正在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
并且请求参数将保持不变。
我遇到了类似的问题,并发现传递带有空字符串的数组将由Rails正确处理,如上所述。如果您在提交表单时遇到此问题,则可能需要包含与数组参数匹配的空隐藏字段:
<input type="hidden" name="model[attribute_ids][]"/>
当实际参数为空时,控制器将始终看到一个带有空字符串的数组,从而使提交无状态。