如何使Sinatra正确处理其中包含数组的JSON请求?

时间:2018-09-25 16:47:55

标签: ruby sinatra

我正在使用javascript将信息发布到Sinatra:

var payload = {
  cart: [ 
    {qty: 1, product: { name: 'baseball' },
    {qty: 3, product: { name: 'soccer' } 
  ]
}

$.post("/endpoint", payload, submit)

在Sinatra中,params产生以下内容:

{"cart" => {
  "0"=>{"qty"=>"1", "product"=> {"name"=>"baseball"} },
  "1"=>{"qty"=>"3", "product"=> {"name"=>"soccer"} }
}}

我该如何使参数变成这样?

{ 
  :cart => [
    { :qty => 1, :product => { :name => "baseball" } },
    { :qty => 3, :product => { :name => "soccer" } },
  ]
} 

3 个答案:

答案 0 :(得分:2)

cart_params = {"cart" => {
  "0"=>{"qty"=>"1", "product"=> {"name"=>"baseball"} },
  "1"=>{"qty"=>"3", "product"=> {"name"=>"soccer"} }
}}

> hash = JSON.parse(cart_params.to_json, symbolize_names: true)
> hash[:cart] = hash[:cart].values
> hash
#=> {
#    :cart=>[
#            {:qty=>"1", :product=>{:name=>"baseball"}}, 
#            {:qty=>"3", :product=>{:name=>"soccer"}}
#          ]
#   }

答案 1 :(得分:2)

要解决此问题,您需要了解的第一件事是您当前发送JSON。 jQuery使用param() function将数据序列化为application/x-www-form-urlencoded,并正在发送(实际上是在发送它的转义版本):

cart[0][qty]=1&cart[0][product][name]=baseball&cart[1][qty]=3&cart[1][product][name]=soccer

param()的文档具有以下注释:

  

注意:由于某些框架解析序列化数组的能力有限,因此开发人员在传递包含嵌套在另一个数组中的对象或数组的obj参数时应格外小心。

  

注意:由于没有普遍认可的参数字符串规范,因此无法使用此方法以理想的方式在支持这种输入的所有语言上工作的方式来编码复杂的数据结构。改用JSON格式代替编码复杂数据。

这是您面临的问题。 Sinatra尝试将经过表单编码的数据解析为Ruby数据结构,但是期望使用不同的格式。它使用Rack::Utils.parse_nested_query来生成您从该查询字符串中看到的参数。

正如jQuery文档所建议的那样,解决方案是将数据作为JSON发送。这样可以确保浏览器和服务器之间的数据结构不会被破坏。

第一步是让浏览器发送JSON。您可以使用post()函数的另一种形式来执行此操作,指定内容类型并将数据转换为JSON字符串:

$.post({url: "/endpoint", data: JSON.stringify(payload), contentType: "application/json", success: submit});

默认情况下,Sinatra将忽略任何内容类型为JSON的请求,并由您处理,因此解决方案的另一半是让Sinatra解析JSON,并可选地填充params哈希。

使用Rack contrib模块PostBodyContentTypeParser的一种方式。只需将以下内容添加到您的应用中(您将需要先安装rack-contrib gem):

require 'rack/contrib/post_body_content_type_parser'

use Rack::PostBodyContentTypeParser

现在将解析任何具有JSON内容类型的POST或PUT请求,并将内容添加到params哈希中。但是,当前发布的版本尚无将键转换为符号的方法(主版本有所更改,但尚未发布),因此它将产生如下内容:

{
  "cart"=>[
    {"qty"=>1, "product"=>{"name"=>"baseball"}},
    {"qty"=>3, "product"=>{"name"=>"soccer"}}
  ]
}

如果要获取符号键,则必须“手动”完成。在您的路线中,您可以添加:

request.body.rewind # Just in case some middleware has already read it
data = JSON.parse(request.body.read, symbolize_names: true)

(如果需要,可以将其添加到before过滤器中,只需添加内容类型实际上是JSON的检查即可。)

这不会将数据添加到params哈希中,但是会以您想要的形式为您提供数据。

答案 2 :(得分:1)

不久前,我遇到了同样的问题,并且我创建了gem来做到这一点(还有许多其他事情可以简化深层嵌套的结构的迭代/映射)。选中Iteraptor

json = {"cart" => {
  "0"=>{"qty"=>"1", "product"=> {"name"=>"baseball"} },  
  "1"=>{"qty"=>"3", "product"=> {"name"=>"soccer"} }  
}}
json.aplanar.recoger(symbolize_keys: true)
#⇒ {:cart=>[
#    {:qty=>"1", :product=>{:name=>"baseball"}},
#    {:qty=>"3", :product=>{:name=>"soccer"}}]}

您可能会检查源代码,以了解手动完成所需的结果并不简单。