2013-02-01 141 views
55

我的应用程序中有一个Backbone模型,它不是一个典型的扁平对象,它是一个大型嵌套对象,我们将嵌套的部分存储在MySQL数据库的TEXT列中。Rails在请求的参数中将空数组转换为nils

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

+0

我发现它在做什么deep_munge https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288。仍然不确定它为什么这样做。 – Karolis

+0

链接到master/actionpack不再指向正确的线路。链接到标签或提交。 –

回答

38

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

+2

如果我使用较旧的rails版本,您知道如何禁用它吗? –

9

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

如果您知道空数组将是你总是可以params[:...][:...] ||= []在过滤器前。

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

丑陋,但它会工作。

+0

这就是它最初的工作原理,我在Backbone中解析/字符串化。但是我认为在发布时没有部分字符串化的JSON是很好的 - 看起来很酷,只需发布​​一个大的JSON,就好像我正在与基于..文档的商店API交谈一样。 – Karolis

+0

Opps ..输入提交这个textarea。现在,我刚刚在https://github.com/rails/rails/pull/8862中讨论了补丁ActionDispatch :: Request.deep_munge,直到它在Rails稳定版中推出,这可能是因为它们合并了PR 2天前。 – Karolis

7

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

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 

这是通过寻找与一个JSON内容类型的请求,重新解析请求的身体,然后拦截params方法返回重新分析的参数(如果存在)。

+3

我发现[this gist](https://gist.github.com/imanel/e01089009479a1596056)与Rails 3.2.13一起工作。 – ciastek

+0

@ciastek伟大的主旨! – mateusmaso

+3

@Grandpa将重新包装的参数与原始参数合并不是更好吗? (例如,url请求参数将会丢失) –

3

我遇到了类似的问题。

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

所以最好你PARAMS想

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

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

-5

这是一种解决此问题的方法。

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 

然后就去做

fixed_params = fix_nils params 

这只要你没有在上有目标的PARAMS尼尔斯工作。

+0

这非常糟糕,因为它会将_all_ nils更改为空数组。这个问题必须在解析时被修正,它不能在解析后修复,因为你无法区分实际的空值和空数组。对于有人将'null'作为值传入是完全有效的,并且这会将其转换为空数组。这只会使问题长期存在,甚至会使情况变得更糟。 – Percy

1

我遇到了类似的问题,发现传递一个空字符串的数组将被Rails正确处理,如上所述。 如果遇到此同时提交表单,你可能要包括该阵列PARAM相匹配的空隐藏字段:

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

当实际参数是空控制器总会看到一个阵列,一个空字符串,从而保持提交无状态。

+0

这帮助我使用隐藏字段提交选定值的多选元素,但是如果没有选择,则没有。该技术让我想起checkbox框架帮助器中的复选框hack。 – febeling

2

这里是(我相信)一个合理的解决方案,不涉及重新解析原始请求正文。这可能无法正常工作,如果你的客户端是张贴表单数据,但在我的情况下,我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 

我碰到了这一点,在我的情况,如果张贴的资源包含两种child_ids: []child_ids: nil我想要的更新意味着“删除所有的孩子”。如果客户不打算更新child_ids列表,则不应在POST正文中发送,在这种情况下,params[:resource].include? attr将为false,请求参数将保持不变。

+1

请注意,这不适用于PATCH – aceofspades

+1

@aceofspades我认为您是对的!虽然如果客户端发送了一个'PATCH'来移除所有关系到另一个资源,比如'{user:{child_ids:[]}}',那么这个解决方案可以工作吗? –

+1

否则,如果请求方法是“PATCH”,则上面的代码可以更新为立即返回。不幸的是,确定参数是否被发送无效,或者如果客户端没有发送PATCH,这并不容易。 –