Ruby:如何将散列转换为 HTTP 参数?

Posted

技术标签:

【中文标题】Ruby:如何将散列转换为 HTTP 参数?【英文标题】:Ruby: How to turn a hash into HTTP parameters? 【发布时间】:2010-10-22 08:57:09 【问题描述】:

使用像

这样的普通哈希非常容易
:a => "a", :b => "b" 

这将转化为

"a=a&b=b"

但是你如何处理更复杂的事情,比如

:a => "a", :b => ["c", "d", "e"] 

应该翻译成

"a=a&b[0]=c&b[1]=d&b[2]=e" 

或者更糟糕的是,(做什么)类似:

:a => "a", :b => [:c => "c", :d => "d", :e => "e", :f => "f"]

感谢您对此提供的非常感谢的帮助!

【问题讨论】:

听起来您想将 JSON 转换为 HTTP 参数...也许您需要不同的编码? 嗯,这实际上不是 Json,而是一个 Ruby 哈希......我不明白为什么编码在这里很重要。 lmanners的回答应该推广。这里有很多很棒的自己动手的答案(很多得分很高),但 ActiveSupport 已经为此添加了标准化支持,从而使对话变得毫无意义。不幸的是,lmner 的答案仍然在列表中。 @Noach 在我看来,任何说依赖于大量猴子补丁核心类的库的答案都应该被埋没。大量这些补丁的理由充其量是不稳定的(看看Yehuda Katz's comments in this article),这是一个很好的例子。 YMMV,但对我来说,是带有类方法或不打开 Object 和 Hash 的东西,而且作者不会说“不要与我们发生冲突!”会好很多。 【参考方案1】:

对于基本的非嵌套哈希,Rails/ActiveSupport 有Object#to_query

>> :a => "a", :b => ["c", "d", "e"].to_query
=> "a=a&b%5B%5D=c&b%5B%5D=d&b%5B%5D=e"
>> CGI.unescape(:a => "a", :b => ["c", "d", "e"].to_query)
=> "a=a&b[]=c&b[]=d&b[]=e"

http://api.rubyonrails.org/classes/Object.html#method-i-to_query

【讨论】:

为什么说它坏了?你显示的输出没问题吧? 我刚试过,你似乎是对的。也许我的陈述最初是由于早期版本的 rails 解析查询字符串的方式(我似乎记得它覆盖了以前的 'b' 值)。在 2011 年 3 月 10 日 11:19:40 -0600 开始 GET "/?a=a&b%5B%5D=c&b%5B%5D=d&b%5B%5D=e" for 127.0.0.1 由 SitesController#index 处理HTML 参数:"a"=>"a", "b"=>["c", "d", "e"] 如果存在嵌套哈希会出现什么问题?为什么有嵌套哈希时我不能使用它?对我来说,它只是 url 转义了嵌套的哈希,在 http 请求中使用它应该没有问题。 不带 Rails:需要require 'active_support/all' 至少 Rails 5.2 to_query 不能正确处理 nil 值。 a: nil, b: '1'.to_query == "a=&b=1",但 Rack 和 CGI​​ 都将 a= 解析为空字符串,而不是 nil。我不确定是否支持其他服务器,但是使用 rails,正确的查询字符串应该是 a&b=1。我认为 Rails 不能生成自己正确解析的查询字符串是错误的……【参考方案2】:

如果您使用的是 Ruby 1.9.2 或更高版本,如果您不需要数组,则可以使用 URI.encode_www_form

例如(来自 1.9.3 中的 Ruby 文档):

URI.encode_www_form([["q", "ruby"], ["lang", "en"]])
#=> "q=ruby&lang=en"
URI.encode_www_form("q" => "ruby", "lang" => "en")
#=> "q=ruby&lang=en"
URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")
#=> "q=ruby&q=perl&lang=en"
URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])
#=> "q=ruby&q=perl&lang=en"

您会注意到数组值没有使用包含[] 的键名来设置,就像我们在查询字符串中已经习惯的那样。 encode_www_form 使用的规范符合application/x-www-form-urlencoded 数据的 HTML5 定义。

【讨论】:

+1,这是迄今为止最好的。它不依赖于 Ruby 本身之外的任何资源。 +1 适用于 ':a => "a", :b => :c => "c", :d => true, :e => [] ' 例子 似乎不适用于 ruby​​ 2.0 - 哈希 :c => "c", :d => true 似乎已被检查,因此作为字符串发送。 这是上面较大的 sn-p 的一部分 - ruby -ruri -e 'puts RUBY_VERSION; puts URI.encode_www_form(:a => "a", :b => :c => "c", :d => true, :e => [])' # outputs 2.0.0 a=a&b=%7B%3Ac%3D%3E%22c%22%2C+%3Ad%3D%3Etrue%7D& 请注意,这对于数组值的结果与 Addressable::URI 和 ActiveSupport 的 Object#to_query 不同。【参考方案3】:

更新:此功能已从 gem 中删除。

Julien,你的自我回答很好,我无耻地借鉴了它,但它不能正确地转义保留字符,而且还有一些其他极端情况会崩溃。

require "addressable/uri"
uri = Addressable::URI.new
uri.query_values = :a => "a", :b => ["c", "d", "e"]
uri.query
# => "a=a&b[0]=c&b[1]=d&b[2]=e"
uri.query_values = :a => "a", :b => [:c => "c", :d => "d", :e => "e", :f => "f"]
uri.query
# => "a=a&b[0][c]=c&b[0][d]=d&b[1][e]=e&b[1][f]=f"
uri.query_values = :a => "a", :b => :c => "c", :d => "d"
uri.query
# => "a=a&b[c]=c&b[d]=d"
uri.query_values = :a => "a", :b => :c => "c", :d => true
uri.query
# => "a=a&b[c]=c&b[d]"
uri.query_values = :a => "a", :b => :c => "c", :d => true, :e => []
uri.query
# => "a=a&b[c]=c&b[d]"

宝石是'addressable'

gem install addressable

【讨论】:

谢谢!我的解决方案出现问题的边缘情况是什么?所以我可以将它添加到规格中吗? 它不处理布尔值,这显然是不可取的:"a" => "a&b=b".to_params 仅供参考,不幸的是,从 2.3 (github.com/sporkmonger/addressable/commit/…) 起,此行为已从 Addressable 中删除 @oif_vet 你能说一下删除了什么行为吗?从 addressable-2.3.2 开始,Bob 建议的使用 addressable gem 解决原始海报问题的方法对我有效。 @sheldonh,不,@oif_vet 是正确的。我删除了这种行为。 Addressable 不再支持深度嵌套的结构作为query_values mutator 的输入。【参考方案4】:

无需加载臃肿的 ActiveSupport 或自行运行,您可以使用 Rack::Utils.build_queryRack::Utils.build_nested_query。 Here's a blog post 就是一个很好的例子:

require 'rack'

Rack::Utils.build_query(
  authorization_token: "foo",
  access_level: "moderator",
  previous: "index"
)

# => "authorization_token=foo&access_level=moderator&previous=index"

它甚至可以处理数组:

Rack::Utils.build_query( :a => "a", :b => ["c", "d", "e"] )
# => "a=a&b=c&b=d&b=e"
Rack::Utils.parse_query _
# => "a"=>"a", "b"=>["c", "d", "e"]

或者更难嵌套的东西:

Rack::Utils.build_nested_query( :a => "a", :b => [:c => "c", :d => "d", :e => "e", :f => "f"]  )
# => "a=a&b[][c]=c&b[][d]=d&b[][e]=e&b[][f]=f"
Rack::Utils.parse_nested_query _
# => "a"=>"a", "b"=>["c"=>"c", "d"=>"d", "e"=>"e", "f"=>"f"]

【讨论】:

您的嵌套示例表明它不能正常工作——当您开始时,:b 是一个包含两个哈希的数组。你最终会得到 :b 是一个更大的哈希数组。 @EdRuder 没有正确,因为没有公认的标准。从其他答案来看,它确实表明它比其他任何人的尝试都更接近。 该方法自 Rails 2.3.8 起已弃用:apidock.com/rails/Rack/Utils/build_query @davidgoli Erm,不在 Rack 中,它不是 github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140。如果你想在 Rails 中使用它,肯定就像require 'rack' 一样简单吗?考虑到现在所有主要的 Ruby Web 框架都构建在 Rack 之上,它肯定存在。 @EdRuder ActiveSupport 的 to_query 也合并了 2 个数组 (v4.2)。【参考方案5】:

如果您只需要支持简单的 ASCII 键/值查询字符串,这里有一个简短而贴心的语句:

hash = "foo" => "bar", "fooz" => 123
# => "foo"=>"bar", "fooz"=>123
query_string = hash.to_a.map  |x| "#x[0]=#x[1]" .join("&")
# => "foo=bar&fooz=123"

【讨论】:

【参考方案6】:

从 Merb 窃取:

# File merb/core_ext/hash.rb, line 87
def to_params
  params = ''
  stack = []

  each do |k, v|
    if v.is_a?(Hash)
      stack << [k,v]
    else
      params << "#k=#v&"
    end
  end

  stack.each do |parent, hash|
    hash.each do |k, v|
      if v.is_a?(Hash)
        stack << ["#parent[#k]", v]
      else
        params << "#parent[#k]=#v&"
      end
    end
  end

  params.chop! # trailing &
  params
end

见http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html

【讨论】:

不幸的是,当我们在参数中有一个嵌套数组时,这个deos不起作用(参见示例#2)...... :( 并且不做任何逃跑之王。【参考方案7】:
class Hash
  def to_params
    params = ''
    stack = []

    each do |k, v|
      if v.is_a?(Hash)
        stack << [k,v]
      elsif v.is_a?(Array)
        stack << [k,Hash.from_array(v)]
      else
        params << "#k=#v&"
      end
    end

    stack.each do |parent, hash|
      hash.each do |k, v|
        if v.is_a?(Hash)
          stack << ["#parent[#k]", v]
        else
          params << "#parent[#k]=#v&"
        end
      end
    end

    params.chop! 
    params
  end

  def self.from_array(array = [])
    h = Hash.new
    array.size.times do |t|
      h[t] = array[t]
    end
    h
  end

end

【讨论】:

【参考方案8】:
:a=>"a", :b=>"b", :c=>"c".map |x,v| "#x=#v" .reduce|x,v| "#x&#v" 

"a=a&b=b&c=c"

这是另一种方式。用于简单查询。

【讨论】:

你真的应该确保你正确地对你的键和值进行 URI 转义。即使是简单的情况。它会咬你的。【参考方案9】:

我知道这是一个老问题,但我只是想发布这段代码,因为我找不到一个简单的 gem 来为我完成这项任务。

module QueryParams

  def self.encode(value, key = nil)
    case value
    when Hash  then value.map  |k,v| encode(v, append_key(key,k)) .join('&')
    when Array then value.map  |v| encode(v, "#key[]") .join('&')
    when nil   then ''
    else            
      "#key=#CGI.escape(value.to_s)" 
    end
  end

  private

  def self.append_key(root_key, key)
    root_key.nil? ? key : "#root_key[#key.to_s]"
  end
end

在这里汇总为宝石:https://github.com/simen/queryparams

【讨论】:

URI.escape != CGI.escape 和你想要的第一个 URL。 实际上不是,@Ernest。当例如将另一个 url 作为参数嵌入到您的 url(假设这是登录后要重定向到的返回 url) URI.escape 将保留“?”并且嵌入 url 的 '&' 会破坏周围的 url,而 CGI.escape 将正确地将它们隐藏起来,以便稍后作为 %3F 和 %26。 CGI.escape("http://localhost/search?q=banana&amp;limit=7")=&gt; "http%3A%2F%2Flocalhost%2Fsearch%3Fq%3Dbanana%26limit%3D7"URI.escape("http://localhost/search?q=banana&amp;limit=7")=&gt; "http://localhost/search?q=banana&amp;limit=7"【参考方案10】:

最好的方法是使用 Hash.to_params,它可以很好地处理数组。

a: 1, b: [1,2,3].to_param
"a=1&b[]=1&b[]=2&b[]=3"

【讨论】:

不带 Rails:需要require 'active_support/all'【参考方案11】:
require 'uri'

class Hash
  def to_query_hash(key)
    reduce() do |h, (k, v)|
      new_key = key.nil? ? k : "#key[#k]"
      v = Hash[v.each_with_index.to_a.map(&:reverse)] if v.is_a?(Array)
      if v.is_a?(Hash)
        h.merge!(v.to_query_hash(new_key))
      else
        h[new_key] = v
      end
      h
    end
  end

  def to_query(key = nil)
    URI.encode_www_form(to_query_hash(key))
  end
end

2.4.2 :019 > :a => "a", :b => "b".to_query_hash(nil)
 => :a=>"a", :b=>"b"

2.4.2 :020 > :a => "a", :b => "b".to_query
 => "a=a&b=b"

2.4.2 :021 > :a => "a", :b => ["c", "d", "e"].to_query_hash(nil)
 => :a=>"a", "b[0]"=>"c", "b[1]"=>"d", "b[2]"=>"e"

2.4.2 :022 > :a => "a", :b => ["c", "d", "e"].to_query
 => "a=a&b%5B0%5D=c&b%5B1%5D=d&b%5B2%5D=e"

【讨论】:

【参考方案12】:

如果您处于 Faraday 请求的上下文中,您也可以将 params 哈希作为第二个参数传递,并且 faraday 会负责将正确的 param URL 部分用于其中:

faraday_instance.get(url, params_hsh)

【讨论】:

【参考方案13】:

我喜欢使用这个 gem:

https://rubygems.org/gems/php_http_build_query

示例用法:

puts PHP.http_build_query("a"=>"b","c"=>"d","e"=>["hello"=>"world","bah"=>"black","hello"=>"world","bah"=>"black"])

# a=b&c=d&e%5B0%5D%5Bbah%5D=black&e%5B0%5D%5Bhello%5D=world&e%5B1%5D%5Bbah%5D=black&e%5B1%5D%5Bhello%5D=world

【讨论】:

【参考方案14】:
2.6.3 :001 > hash = :a => "a", :b => ["c", "d", "e"]
=> :a=>"a", :b=>["c", "d", "e"]
2.6.3 :002 > hash.to_a.map  |x| "#x[0]=#x[1].class == Array ? x[1].join(",") : x[1]" 
.join("&")
=> "a=a&b=c,d,e"

【讨论】:

以上是关于Ruby:如何将散列转换为 HTTP 参数?的主要内容,如果未能解决你的问题,请参考以下文章

如何从我的 Ruby on Rails API 解析这些散列转换为字符串,以便它可以作为 Javascript 中的对象进行访问?

如何使用 Apache Shiro 将散列密码存储到数据库中?

如何使用 Laravel 5.8 将散列密码存储到数据库中

如何合并散列数组以获取值数组的散列

Ruby to_json 出现错误“非法/格式错误的 utf-8”问题

是否有一个转换Vec的好方法 到阵列? [重复]