【问题标题】:How can you get Sinatra to properly process a JSON request that has an array in it?如何让 Sinatra 正确处理包含数组的 JSON 请求?
【发布时间】:2018-09-25 16:47:54
【问题描述】:

我正在使用 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" } },
  ]
} 

【问题讨论】:

    标签: ruby sinatra


    【解决方案1】:

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

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

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

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

    注意:因为没有普遍认可的参数字符串规范,所以不可能使用这种方法以一种在所有支持这种输入的语言中都能理想地工作的方式对复杂的数据结构进行编码。使用 JSON 格式替代编码复杂数据。

    这是您面临的问题。 Sinatra 尝试将 form-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,并可选择填充 params 哈希。

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

    require 'rack/contrib/post_body_content_type_parser'
    
    use Rack::PostBodyContentTypeParser
    

    现在将解析任何具有 JSON 内容类型的 POST 或 PUT 请求,并将内容添加到 params 哈希中。但是,当前发布的版本没有将键转换为符号的方法(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 哈希中,但会以您正在寻找的形式为您提供数据。

    【讨论】:

      【解决方案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"}}
      #          ]
      #   }
      

      【讨论】:

        【解决方案3】:

        前段时间我遇到了同样的问题,我创建了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"}}]}
        

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

        【讨论】:

        • 谢谢!但我只是好奇,这个 Gem 优化到什么程度了?
        • 如果数据来自 AJAX 调用,则不会成为瓶颈。如有疑问,请随时检查来源。
        猜你喜欢
        • 2015-10-03
        • 2017-10-16
        • 2018-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多