Ruby: Bir karma nasıl HTTP parametrelerine dönüştürülür?


205

Bu gibi sade bir hash ile oldukça kolay

{:a => "a", :b => "b"} 

hangi tercüme edecek

"a=a&b=b"

Ama daha karmaşık bir şeyle ne yaparsın

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

hangisine dönüşmeli

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

Veya daha da kötüsü, (ne yapmalı) gibi bir şeyle:

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

Bununla ilgili çok takdir edilen yardım için teşekkürler!


JSON'u HTTP parametrelerine dönüştürmek istediğiniz gibi geliyor ... belki de farklı bir kodlamaya ihtiyacınız var?
CookieOfFortune

Hum, bu aslında Json değil, bir Ruby Hash ... burada kodlamanın neden önemli olduğunu anladığımdan emin değilim.
Julien Genestoux

Liderlerin cevabı teşvik edilmelidir. Burada birçok harika cevap var (çoğu yüksek puanlı), ancak ActiveSupport o zamandan beri bunun için standart destek ekledi ve konuşma tartışmalarını gerçekleştirdi. Ne yazık ki, lmanner'ın cevabı hala listede yer alıyor.
Noach Magedman

2
@ Bence, ağır maymun yamaları çekirdek sınıflarının bir kütüphaneye güvendiğini söyleyen herhangi bir cevap gömülü kalmalıdır. Bu yamaların çok sayıda gerekçesi en iyi ihtimalle titrektir ( bu makaledeki Yehuda Katz'ın yorumlarına bir göz atın ), bu mükemmel bir örnektir. YMMV, ama benim için, bir sınıf yöntemine sahip olan veya Object ve Hash'i açmayan ve yazarların "bizimle çatışmayın!" çok, çok daha iyi olurdu.
Iain

Yanıtlar:


86

Güncelleme: Bu işlevsellik taştan kaldırıldı.

Julien, kendi cevabın iyi bir cevap, ve ben utanmadan ödünç aldım, ama ayrılmış karakterlerden düzgün bir şekilde kaçmıyor ve bozulduğu birkaç başka durum daha var.

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

Mücevher ' adreslenebilir '

gem install addressable

1
Teşekkürler! Çözümümün kırıldığı uç durumlar nelerdir? bu yüzden özellikleri ekleyebilirsiniz?
Julien Genestoux

2
Boolean'larla başa çıkmaz ve bu kesinlikle istenmeyen bir durumdur: {"a" => "a & b = b"}. To_params
Bob Aman

3
Bilginize, ne yazık ki bu davranış 2.3 itibariyle Adreslenebilir'den kaldırıldı ( github.com/sporkmonger/addressable/commit/… )
oif_vet

2
@oif_vet Hangi davranışın kaldırıldığını söyleyebilir misiniz? Bob'un orijinal posterin problemini çözmek için adreslenebilir mücevheri kullanma önerisi benim için adreslenebilir-2.3.2'den beri çalışıyor.
sheldonh

1
@sheldonh, hayır, @oif_vet doğru. Bu davranışı kaldırdım. Derin yuvalanmış yapılar artık query_valuesmutasyona giriş olarak Adreslenebilir'de desteklenmemektedir .
Bob Aman

269

Temel, iç içe olmayan karmalar için Rails / ActiveSupport'a sahiptir 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
Neden kırıldığını söylüyorsun? gösterdiğiniz çıktı iyi, değil mi?
tokland

Sadece denedim ve haklısın. Belki benim deyim başlangıçta rayların önceki bir sürümü sorgu dizesini ayrıştırma yolu nedeniyle (önceki 'b' değerlerinin üzerine yazmayı hatırlatmak gibiydi). 2011-03-10 11:19:40 -0600 SitesController # index'de 127.0.0.1 için GET "/? A = a & b% 5B% 5D = c & b% 5B% 5D = d & b% 5B% 5D = e" olarak başladı HTML Parametreleri: {"a" => "a", "b" => ["c", "d", "e"]}
Gabe Martin-Dempesy

yuvalanmış karma varsa yanlış giden ne olur? İç içe geçmiş karmalar olduğunda bunu neden kullanamıyorum? Bana göre, sadece url iç içe karmadan kaçar, http isteğinde bunu kullanırken sorun olmamalıdır.
Sam

2
Raysız: require 'active_support/all'gerekli
Dorian

En azından Rails 5.2 ile to_querysıfır değerleri düzgün işlemez. { a: nil, b: '1'}.to_query == "a=&b=1", ancak Rack ve CGI a=boş bir dize olarak ayrışır, değil nil. Diğer sunucular için destek hakkında emin değilim, ancak raylar ile doğru sorgu dizesi olmalıdır a&b=1. Rails doğru kendiliğinden ayrıştırılan bir sorgu dizesi üretemez yanlış olduğunu düşünüyorum ...
jsmartt

153

Ruby 1.9.2 veya üstünü kullanıyorsanız, URI.encode_www_formdizilere ihtiyacınız yoksa kullanabilirsiniz .

Örneğin (1.9.3'teki Ruby belgelerinden):

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"

Dizi değerlerinin, []hepimiz sorgu dizelerinde alışkın olduğumuz gibi anahtar adlarla ayarlanmadığını fark edeceksiniz . Kullanılan encode_www_formözellik, application/x-www-form-urlencodedverilerin HTML5 tanımına uygundur .


8
+1, bu en iyisi. Ruby'nin dışındaki hiçbir kaynağa bağlı değildir.
Danyel

+1, '{: a => "a",: b => {: c => "c",: d => true},: e => []}' örneğiyle iyi çalışır
Duke

1
Ruby 2.0 ile çalışmıyor gibi görünüyor - karma {:c => "c", :d => true}denetlenir, bu yüzden bir dize olarak gönderilir.
user208769

1
Yukarıdaki büyük snippet'in bir bölümü oldu -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
Bunun dizi değerleri için hem Addressable::URIActiveSupport'lardan hem de farklı sonuçlar verdiğini unutmayın Object#to_query.
Matt Huggins

61

Şişirilmiş ActiveSupport'u yüklemenize veya kendiniz yuvarlamanıza gerek yok, Rack::Utils.build_queryve kullanabilirsiniz Rack::Utils.build_nested_query. İşte iyi bir örnek veren bir blog yazısı :

require 'rack'

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

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

Dizileri bile işler:

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

Veya daha zor iç içe geçmiş şeyler:

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

Yuvalanmış örneğiniz, düzgün çalışmadığını gösterir - başlattığınızda :biki karmadan oluşan bir dizidir. Sonunda :bdaha büyük bir karma dizisi olursunuz.
Ed Ruder

3
@EdRuder uygun bir şey yok çünkü kabul edilen bir standart yok. Gösterdiği şey, diğer cevaplara bakarak, başkalarının girişiminden çok daha yakın olduğudur.
iain

1
: Bu yöntem Raylar 2.3.8 beri kullanımdan kaldırıldı apidock.com/rails/Rack/Utils/build_query
davidgoli

8
@davidgoli Erm, değil Rack o değil github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140 . Rails'te kullanmak istiyorsanız, kesinlikle bu kadar basit require 'rack'mi? Tüm büyük Ruby web çerçevelerinin şimdi Rack'in üzerine inşa edildiği göz önüne alındığında orada olmalı.
iain

@EdRuder ActiveSupport's to_queryda 2 diziyi birleştirir (v4.2).
Kelvin

9

Merb'den çalmak:

# 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

Bkz. Http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html


1
Ne yazık ki, bu
deos paramların

2
Ve kaçma kralı yapmaz.
Ernest

9

İşte basit ASCII anahtar / değer sorgu dizelerini desteklemeniz gerekiyorsa kısa ve tatlı bir astar:

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"

İşte başka bir yol. Basit sorgular için.


2
anahtarlarınızı ve değerlerinizi doğru bir şekilde URI'den kaçtığınızdan emin olmalısınız. Basit durumlar için bile. Seni ısıracak.
jrochkind

2

Bu eski bir soru olduğunu biliyorum, ama sadece bu görevi benim için yapmak için basit bir mücevher bulamadık gibi ben sadece kod biraz göndermek istedim.

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

Buraya mücevher olarak yerleştirilmiş: https://github.com/simen/queryparams


1
URI.escape != CGI.escapeve URL için birincisini istiyorsunuz.
Ernest

2
Aslında hayır, @Ernest. Örneğin, URL'nize parametre olarak başka bir URL yerleştirirken (bunun giriş yaptıktan sonra yönlendirilecek dönüş URL'si olduğunu varsayalım) URI.escape '?' CGI.escape onları daha sonra% 3F ve% 26 olarak doğru bir şekilde saklayacaktır. 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

En iyi yaklaşım dizilerle iyi çalışan Hash.to_params kullanmaktır.

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

Raysız: require 'active_support/all'gerekli
Dorian

1

Bir Faraday isteği bağlamındaysanız, ikinci argüman olarak params karmasını da iletebilirsiniz ve faraday, uygun param URL'sini bunun bir parçası haline getirmeye özen gösterir:

faraday_instance.get(url, params_hsh)

0

Bu gem kullanmayı seviyorum:

https://rubygems.org/gems/php_http_build_query

Örnek kullanım:

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"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.