Ruby:如何将哈希转换为HTTP参数?


205

像这样简单的哈希就很容易

{: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参数...也许您需要其他编码?
CookieOfFortune

嗯,这实际上不是Json,而是Ruby Hash ...不确定我理解为什么编码在这里很重要。
Julien Genestoux 09年

lmanners的答案应该得到推广。这里有很多很棒的答案(很多人得分很高),但是ActiveSupport从那时起为此添加了标准化支持,使讨论变得毫无意义。不幸的是,lmanner的答案仍然落在列表中。
Noach Magedman,

2
@Noach在我看来,任何依赖于依赖大量猴子修补核心类的库的答案都应该保留。大量补丁的理由充其量是不稳定的(请看本文中Yehuda Katz的评论),这是一个很好的例子。YMMV,但对我来说,带有类方法的东西或不能打开Object and Hash的地方,作者不会说“就是不要与我们发生冲突!”。会好得多。
iain

Answers:


86

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

朱利安,您的自答是一个很好的答案,我从中无耻地借鉴了它,但是它不能正确地逃脱保留字符,并且在其他一些情况下,它很容易崩溃。

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

宝石是“ 可寻址的

gem install addressable

1
谢谢!我的解决方案中断的哪些极端情况?所以我可以将其添加到规格中吗?
朱利安·吉恩斯托

2
它不处理布尔值,这显然是不可取的:{“ a” =>“ a&b = b”}。to_params
Bob Aman

3
仅供参考,不幸的是这种行为已经从可寻址除去2.3(的github.com/sporkmonger/addressable/commit/...
oif_vet

2
@oif_vet您能说什么行为已被删除吗?鲍勃建议的使用可寻址宝石解决原始海报问题的方法对我来说适用于2.2.3。
sheldonh

1
@sheldonh,不,@ oif_vet是正确的。我删除了此行为。“可寻址”中不再支持深层嵌套结构作为query_values变量的输入。
Bob Aman

269

对于基本的非嵌套哈希,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


1
你为什么说它坏了?您显示的输出正常,不是吗?
tokland 2011年

我刚刚尝试过,您似乎是对的。也许我的声明最初是由于Rails的早期版本解析查询字符串的方式引起的(我似乎还记得它覆盖了先前的'b'值)。于2011-03-10 11:19:40 -0600在12-03.0.1处开始GET“ /?a = a&b%5B%5D = c&b%5B%5D = d&b%5B%5D = e”由SitesController#index处理为HTML参数:{“ a” =>“ a”,“ b” => [“ c”,“ d”,“ e”]}
Gabe Martin-Dempesy 2011年

如果存在嵌套哈希,怎么办?当嵌套的哈希值存在时,为什么不能使用此功能?对我来说,它只是url逃脱了嵌套的哈希,在http请求中使用它应该没有问题。
2015年

2
没有Rails:require 'active_support/all'需要
Dorian

至少在Rails 5.2 to_query中不能正确处理nil值。{ a: nil, b: '1'}.to_query == "a=&b=1",但Rack和CGI都解析a=为一个空字符串,而不是nil。我不确定是否支持其他服务器,但是使用rails时,正确的查询字符串应为a&b=1。我认为Rails无法产生正确解析的查询字符串是错误的……
jsmartt

153

如果您使用的是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符合HTML5 application/x-www-form-urlencoded数据定义。


8
+1,这是迄今为止最好的。它不依赖Ruby本身以外的任何来源。
Danyel 2013年

+1适用于'{:a =>“ a”,:b => {:c =>“ c”,:d => true},:e => []}'示例
Duke

1
似乎不适用于ruby 2.0-哈希{:c => "c", :d => true}似乎已被检查,因此以字符串形式发送。
user208769 2013年

1
这是上方较大片段的一部分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&
user208769 2013年

1
请注意,数组值的结果不同于Addressable::URIActiveSupport的结果Object#to_query
马特·哈金斯

61

无需加载the肿的ActiveSupport或自己滚动,可以使用Rack::Utils.build_queryRack::Utils.build_nested_query这是一个博客文章,提供了一个很好的例子:

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得到一个更大的哈希数组。
Ed Ruder

3
@EdRuder没有正确的选择,因为没有公认的标准。从其他答案来看,它确实表明它比任何人的尝试都更加接近。
iain

1
自Rails 2.3.8起不推荐使用此方法:apidock.com/rails/Rack/Utils/build_query
davidgoli 2013年

8
@davidgoli Erm,不在Rack中,它不是github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140。如果您想在Rails中使用它,那么它肯定很简单require 'rack'?考虑到现在所有主要的Ruby Web框架都是基于Rack构建的,因此它必须存在。
iain

@EdRuder ActiveSupport to_query还可以合并两个数组(v4.2)。
开尔文2015年

9

从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


1
不幸的是,当我们在参数中有一个嵌套的数组时,这种方法无效(请参见示例2)... :(
Julien Genestoux 09年

2
并没有做任何逃避之王。
欧内斯特(Ernest)

9

如果您只需要支持简单的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"

4
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

3
{:a=>"a", :b=>"b", :c=>"c"}.map{ |x,v| "#{x}=#{v}" }.reduce{|x,v| "#{x}&#{v}" }

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

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


2
不过,您确实应该确保正确地对URI进行键和值的转义。即使是简单的情况。会咬你的
jrochkind 2014年

2

我知道这是一个老问题,但是我只是想发布这段代码,因为我找不到简单的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

在此处汇总为gem:https//github.com/simen/queryparams


1
URI.escape != CGI.escape对于URL,您需要第一个。
欧内斯特(Ernest)

2
其实不是,@ Ernest。例如,当将另一个URL作为参数嵌入到您的URL中时(假设这是登录后将重定向到的返回URL),URI.escape将保留“?” 和嵌入网址的'&'打破周围的网址,而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"
斯维尔,2012年

2

最好的方法是使用Hash.to_params,它是对数组有效的一种方法。

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

没有Rails:require 'active_support/all'需要
Dorian

1

如果您在法拉第请求的上下文中,还可以仅将params哈希作为第二个参数传递,而faraday负责使其中的适当参数URL成为一部分:

faraday_instance.get(url, params_hsh)

0

我喜欢使用这个宝石:

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

0
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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.