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_query
和 Rack::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&limit=7")
=> "http%3A%2F%2Flocalhost%2Fsearch%3Fq%3Dbanana%26limit%3D7"
URI.escape("http://localhost/search?q=banana&limit=7")
=> "http://localhost/search?q=banana&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 将散列密码存储到数据库中?