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 2009

ええと、これは実際にはJsonではなく、Rubyハッシュです...なぜここでエンコーディングが重要なのか理解できません。
Julien Genestoux 2009

lmannersによる答えは促進されるべきです。ここには多くの優れた独自の答えがあります(多くは高得点です)が、ActiveSupportはこれに対する標準化されたサポートを追加して、会話を混乱させています。残念ながら、lmannerの答えはまだリストに埋もれています。
Noach Magedman、2013年

2
@Noach私の意見では、コアクラスに大量のモンキーパッチを適用するライブラリに依存するという答えは、埋もれたままにする必要があります。これらのパッチの膨大な数の正当化はせいぜい揺れ動きます(この記事でのYehuda Katzのコメントを見てください)。これは優れた例です。YMMVですが、私にとっては、クラスメソッドを持つもの、またはObjectとHashを開かないもの、そして著者が「私たちと衝突しないでください!」はるかに良いでしょう
iain 2013年

回答:


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
どうも!私のソリューションが壊れるエッジケースは何ですか?スペックに追加できますか?
Julien Genestoux、2009

2
ブール値を処理しないため、これは明らかに望ましくありません。{"a" => "a&b = b"}。to_params
Bob Aman

3
FYI、残念ながらこの動作は2.3(のようにアドレス指定から除去されたgithub.com/sporkmonger/addressable/commit/...
oif_vet

2
@oif_vet削除された動作は何ですか?アドレス可能なgemを使用して元の投稿者の問題を解決するというボブの提案された手法は、アドレス可能な-2.3.2の時点で私には有効です。
sheldonh 2013年

1
@sheldonh、いや、@ oif_vetは正しいです。この動作を削除しました。深く入れ子になった構造は、query_valuesミューテーターへの入力としてAddressableでサポートされなくなりました。
ボブ・アマン

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

私はそれを試したところ、あなたは正しいようです。多分私のステートメントは、以前のバージョンのrailsがクエリ文字列を解析した方法が原因だったのかもしれません(以前の「b」の値を上書きしたことを思い出したようです)。2011-03-10 11:19:40 -0600 SitesController#index as HTMLパラメータ:{"a" => "a"、 "b" => ["c"、 "d"、 "e"]}
Gabe Martin-Dempesy

ネストされたハッシュがある場合、何が問題になりますか?ネストされたハッシュがあるときにこれを使用できないのはなぜですか?私にとっては、URLはネストされたハッシュをエスケープするだけです。httpリクエストでこれを使用しても問題はありません。
Sam

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ます。他のサーバーのサポートについてはわかりませんが、レールを使用すると、正しいクエリ文字列はになりますa&b=1。Railsがそれ自体で正しく解析されるクエリ文字列を生成できないのは間違っていると思います...
jsmartt

154

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定義に準拠しています。


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

1
これは、配列値の結果Addressable::URIとActiveSupportの結果の両方とは異なることに注意してくださいObject#to_query
Matt Huggins、2014

61

肥大化し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"}]}

ネストされた例は、それが正しく機能しないことを示しています-開始すると、:b2つのハッシュの配列です。最終的に:bは、1つの大きなハッシュの配列になります。
Ed Ruder 2013

3
@EdRuder 受け入れられた標準がないため、適切にはありません。それが示していることは、他の回答から判断すると、他の誰の試みよりもはるかに近いということです。
iain 2013

1
このメソッドはRails 2.3.8から廃止されました:apidock.com/rails/Rack/Utils/build_query
davidgoli

8
Erm @davidgoli、ではないラック、それはありませんgithub.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140。Railsで使用したい場合は、確かに次のように簡単require 'rack'です。現在、Rackの上にすべての主要なRuby Webフレームワークが構築されていることを考えると、そこにあるはずです。
iain 2013年

@EdRuder ActiveSupport to_queryも2つの配列をマージします(v4.2)。
ケルビン

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
残念ながら、これはparams内にネストされた配列がある場合(例#2を参照)には機能しません... :(
Julien Genestoux

2
そして、脱出の王を行いません。
アーネスト

9

単純なASCIIキー/値のクエリ文字列のみをサポートする必要がある場合の、短くて甘い1つのライナーは次のとおりです。

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

私はこれが古い質問であることを知っていますが、このタスクを実行するための簡単な宝石を見つけることができなかったため、このコードを投稿したいと思いました。

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


1
URI.escape != CGI.escapeそして、あなたは最初のものが欲しいURLのために。
アーネスト

2
@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"
svale

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

Faradayリクエストのコンテキスト内にいる場合は、paramsハッシュを2番目の引数として渡すだけで、faradayが適切なparam 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"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.