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

How can you get Sinatra to properly process a JSON request that has an array in it?

我正在 POST 与 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" } },
  ]
} 

我前一段时间遇到了同样的问题,我创建了 gem 这样做(任何其他简化深层嵌套结构的东西 iteration/mapping)。检查 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"}}]}

您可以查看源代码,了解手动实现所需结果并非易事。

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"}}
#          ]
#   }

要解决这个问题,您首先需要了解的是您目前不是发送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() 的文档有以下注释:

Note: Because some frameworks have limited ability to parse serialized arrays, developers should exercise caution when passing an obj argument that contains objects or arrays nested within another array.

Note: Because there is no universally agreed-upon specification for param strings, it is not possible to encode complex data structures using this method in a manner that works ideally across all languages supporting such input. Use JSON format as an alternative for encoding complex data instead.

这就是您面临的问题。 Sinatra 尝试将表单 urlencoded 数据解析为 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,并可选择填充参数哈希。

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

require 'rack/contrib/post_body_content_type_parser'

use Rack::PostBodyContentTypeParser

现在,任何具有 JSON 内容类型的 POST 或 PUT 请求都将被解析,并将内容添加到参数哈希中。然而,当前发布的版本没有将键转换为符号的方法(master 中有一个更改允许这样做,但尚未发布)所以它会产生类似的东西:

{
  "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 哈希,但会以您正在查找的形式提供数据。