Ruby: Jak zmienić skrót w parametry HTTP?


205

To jest całkiem proste z prostym hash

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

co przełożyłoby się na

"a=a&b=b"

Ale co robisz z czymś bardziej złożonym, takim jak

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

co powinno się przełożyć na

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

Lub jeszcze gorzej (co zrobić) z czymś takim jak:

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

Dzięki za bardzo docenianą pomoc!


Wygląda na to, że chcesz przekonwertować JSON na parametry HTTP ... może potrzebujesz innego kodowania?
— CookieOfFortune

Hum, to właściwie nie jest Json, ale Ruby Hash ... nie jestem pewien, czy rozumiem, dlaczego kodowanie ma tutaj znaczenie.
— Julien Genestoux

Odpowiedź lmannerów powinna być promowana. Istnieje tutaj wiele świetnych odpowiedzi typu „zrób to sam” (wiele z wysokimi wynikami), ale ActiveSupport od tego czasu dodał do tego znormalizowane wsparcie, czyniąc dyskusję dyskusyjną. Niestety, odpowiedź Lmannera jest wciąż schowana na liście.
— Noach Magedman,

2
@Noach, moim zdaniem, każda odpowiedź, która mówi, aby polegać na bibliotece, w której mocno małpowane łaty podstawowe klasy powinny pozostać pochowane. Uzasadnienie ogromnej liczby tych łat jest w najlepszym razie niepewne (spójrz na komentarze Yehudy Katza w tym artykule ), co jest doskonałym przykładem. YMMV, ale dla mnie coś z metodą klasową lub która nie otwiera Object i Hash, i gdzie autorzy nie powiedzieliby „po prostu nie koliduj z nami!” byłoby znacznie, znacznie lepiej.
— iain

Odpowiedzi:


86

Aktualizacja: Ta funkcjonalność została usunięta z klejnotu.

Julien, twoja własna odpowiedź jest dobra, a ja bezwstydnie pożyczyłem od niej, ale nie ucieka właściwie zarezerwowanym postaciom, i jest kilka innych skrajnych przypadków, w których się psuje.

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

Klejnot jest „ adresowalny ”

gem install addressable

1
Dzięki! Jakie są skrajne przypadki, w których moje rozwiązanie psuje się? więc mogę dodać to do specyfikacji?
— Julien Genestoux

2
Nie obsługuje boolanów i jest to zdecydowanie niepożądane: {"a" => "a & b = b"}. To_params
— Bob Aman

3
Do Twojej wiadomości, niestety to zachowanie zostało usunięte z Adresowalnych od 2.3 ( github.com/sporkmonger/addressable/commit/… )
— oif_vet,

2
@oif_vet Czy możesz powiedzieć, jakie zachowanie zostało usunięte? Sugerowana przez Boba metoda użycia klejnotu adresowalnego do rozwiązania problemu z oryginalnym plakatem działa dla mnie od adresowalnego-2.3.2.
— sheldonh

1
@ sheldonh, nie, @oif_vet jest poprawny. Usunąłem to zachowanie. Głęboko zagnieżdżone struktury nie są już obsługiwane w adresowalnych jako dane wejściowe do query_valuesmutatora.
— Bob Aman

269

W przypadku podstawowych, nie zagnieżdżonych skrótów, Rails / ActiveSupport ma 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
Dlaczego mówisz, że jest zepsuty? wynik, który pokazałeś, jest w porządku, prawda?
— tokland

Właśnie tego spróbowałem i wydaje się, że masz rację. Być może moje oświadczenie było pierwotnie spowodowane sposobem, w jaki wcześniejsza wersja szyn parsowała ciąg zapytania (wydawało mi się, że pamiętam, że zastępuje poprzednie wartości „b”). Rozpoczęto GET "/? A = a & b% 5B% 5D = c & b% 5B% 5D = d & b% 5B% 5D = e" dla 127.0.0.1 na 2011-03-10 11:19:40 -0600 Przetwarzanie przez SitesController # indeks jako Parametry HTML: {„a” ​​=> „a”, „b” => [„c”, „d”, „e”]}
— Gabe Martin-Dempesy

co pójdzie nie tak, jeśli są zagnieżdżone skróty? Dlaczego nie mogę tego użyć, gdy są zagnieżdżone skróty? Dla mnie po prostu url ucieka z zagnieżdżonego skrótu, nie powinno być problemu z użyciem tego w żądaniu http.
— Sam

2
Bez szyn: require 'active_support/all'jest potrzebny
— Dorian,

Przynajmniej w Rails 5.2 to_querynie obsługuje poprawnie wartości zerowych. { a: nil, b: '1'}.to_query == "a=&b=1", ale zarówno Rack, jak i CGI parsują a=jako pusty ciąg, a nie nil. Nie jestem pewien co do obsługi innych serwerów, ale w przypadku szyn powinien być prawidłowy ciąg zapytania a&b=1. Myślę, że to źle, że Railsy nie mogą wygenerować ciągu zapytania, który sam jest poprawnie parsowany ...
— jsmartt

153

Jeśli używasz Ruby 1.9.2 lub nowszego, możesz użyć URI.encode_www_form jeśli nie potrzebujesz tablic.

Np. (Z Ruby docs w 1.9.3):

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"

Zauważysz, że wartości tablic nie są ustawione z nazwami kluczy zawierającymi, []jak wszyscy jesteśmy przyzwyczajeni w ciągach zapytań. Używana specyfikacja encode_www_formjest zgodna z definicją application/x-www-form-urlencodeddanych HTML5 .


8
+1, to zdecydowanie najlepsze. Nie zależy od żadnych źródeł poza samym Ruby.
— Danyel

+1 działa dobrze z przykładem „{: a =>" a ",: b => {: c =>" c ",: d => true},: e => []} 'przykład
— Duke

1
{:c => "c", :d => true}Wygląda na to, że nie działa z Ruby 2.0 - skrót wydaje się być sprawdzany, więc jest przesyłany jako ciąg znaków.
— user208769,

1
Była to część większego fragmentu powyżej -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&
— użytkownik208769,

1
Zauważ, że ma to inne wyniki dla wartości tablic niż dla obu Addressable::URIi dla ActiveSupport Object#to_query.
— Matt Huggins

61

Nie musisz ładować rozdętego ActiveSupport ani tworzyć własnych, możesz użyć Rack::Utils.build_queryi Rack::Utils.build_nested_query. Oto post na blogu, który stanowi dobry przykład:

require 'rack'

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

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

Obsługuje nawet tablice:

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

Lub trudniejsze zagnieżdżone rzeczy:

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

Twój zagnieżdżony przykład pokazuje, że nie działa poprawnie - kiedy zaczynasz, :bjest tablicą dwóch skrótów. W efekcie :bpowstaje tablica jednego większego skrótu.
— Ed Ruder

3
@EdRuder nie ma poprawnie, ponieważ nie ma akceptowanego standardu. Pokazuje, że jest o wiele bliżej niż próba kogokolwiek innego, sądząc po innych odpowiedziach.
— iain

1
Ta metoda jest przestarzała od czasu Rails 2.3.8: apidock.com/rails/Rack/Utils/build_query
— davidgoli

8
@davidgoli Erm, nie w Rack, to nie jest github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140 . Jeśli chcesz go używać w Railsach, to z pewnością jest tak proste jak require 'rack'? Musi tam być, biorąc pod uwagę, że wszystkie główne frameworki Ruby są teraz zbudowane na Rack.
— iain

@EdRuder ActiveSupport to_queryłączy także 2 tablice (v4.2).
— Kelvin

9

Kradnij od Merba:

# 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

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


1
Niestety, to deos nie działa, gdy mamy zagnieżdżoną macierz wewnątrz parametrów (patrz przykład # 2) ... :(
— Julien Genestoux

2
I nie robi żadnego króla ucieczki.
— Ernest,

9

Oto krótki i słodki jeden linijka, jeśli potrzebujesz obsługiwać tylko proste ciągi zapytań klucz / wartość 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"

Oto inny sposób. Dla prostych zapytań.


2
naprawdę powinieneś jednak upewnić się, że właściwie unikasz identyfikatora URI swoich kluczy i wartości. Nawet w prostych przypadkach. Ugryzie cię.
— jrochkind

2

Wiem, że to stare pytanie, ale chciałem tylko napisać ten fragment kodu, ponieważ nie mogłem znaleźć prostego klejnotu, który mógłby wykonać to zadanie dla mnie.

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

Zwinięte jako klejnot tutaj: https://github.com/simen/queryparams


1
URI.escape != CGI.escapei dla adresu URL chcesz pierwszy.
— Ernest,

2
Właściwie nie, @Ernest. W przypadku np. Osadzenia innego adresu jako parametru do adresu URL (powiedzmy, że jest to adres zwrotny, na który należy przekierować po zalogowaniu), URI.escape zachowa „?” i „&” osadzonego adresu URL w miejscu, niszcząc otaczający adres URL, podczas gdy CGI.escape poprawnie je ukryje na później jako% 3F i% 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

Najlepszym rozwiązaniem jest użycie Hash.to_params, który działa dobrze z tablicami.

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

Bez szyn: require 'active_support/all'jest potrzebny
— Dorian,

1

Jeśli jesteś w kontekście żądania Faradaya, możesz również przekazać skrót params jako drugi argument i faraday zajmuje się tworzeniem z niego odpowiedniego adresu URL param:

faraday_instance.get(url, params_hsh)

0

Lubię używać tego klejnotu:

https://rubygems.org/gems/php_http_build_query

Przykładowe użycie:

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"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.