Ruby: Come trasformare un hash in parametri HTTP?


205

È abbastanza facile con un semplice hash like

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

che si tradurrebbe in

"a=a&b=b"

Ma cosa fai con qualcosa di più complesso

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

che dovrebbe tradursi in

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

O peggio ancora (cosa fare) con qualcosa del tipo:

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

Grazie per l'aiuto molto apprezzato!


Sembra che tu voglia convertire JSON in parametri HTTP ... forse hai bisogno di una codifica diversa?
CookieOfFortune,

Hum, questo in realtà non è Json, ma un Ruby Hash ... non sono sicuro di capire perché la codifica sia importante qui.
Julien Genestoux,

La risposta degli lmanner dovrebbe essere promossa. Ci sono molte ottime risposte roll-your-own qui (molte con punteggi alti) ma ActiveSupport da allora ha aggiunto un supporto standardizzato per questo, rendendo discutibile la conversazione. Sfortunatamente, la risposta di lmanner è ancora sepolta nell'elenco.
Noach Magedman,

2
@Noach secondo me, qualsiasi risposta che dice di fare affidamento su una libreria che le classi core pesantemente delle scimmie dovrebbe rimanere sepolta. La giustificazione per un numero enorme di quelle patch è alquanto traballante (dai un'occhiata ai commenti di Yehuda Katz in questo articolo ), questo è un eccellente esempio. YMMV, ma per me qualcosa con un metodo di classe o che non apre Object e Hash, e dove gli autori non direbbero "semplicemente non scontrarti con noi!" sarebbe molto, molto meglio.
Iain

Risposte:


86

Aggiornamento: questa funzionalità è stata rimossa dalla gemma.

Julien, la tua auto-risposta è buona, e ne ho preso spudoratamente in prestito, ma non sfugge correttamente ai personaggi riservati, e ci sono alcuni altri casi limite in cui si rompe.

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

La gemma è " indirizzabile "

gem install addressable

1
Grazie! Quali sono i casi limite in cui si rompe la mia soluzione? quindi posso aggiungerlo alle specifiche?
Julien Genestoux,

2
Non gestisce i booleani, e questo è chiaramente indesiderabile: {"a" => "a & b = b"}. To_params
Bob Aman,

3
Cordiali saluti, purtroppo questo comportamento è stato rimosso da Indirizzabile dal 2.3 ( github.com/sporkmonger/addressable/commit/… )
oif_vet

2
@oif_vet Potresti dire quale comportamento è stato rimosso? La valutazione suggerita da Bob di usare la gemma indirizzabile per risolvere il problema del poster originale funziona per me come indirizzabile-2.3.2.
sheldonh,

1
@sheldonh, no, @oif_vet è corretto. Ho rimosso questo comportamento. Le strutture profondamente annidate non sono più supportate in Indirizzabile come input per il query_valuesmutatore.
Bob Aman,

269

Per hash di base, non nidificati, Rails / ActiveSupport ha 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
Perché dici che è rotto? l'output che hai mostrato è ok, vero?
tokland

L'ho appena provato e sembra che tu abbia ragione. Forse la mia affermazione era originariamente dovuta al modo in cui una versione precedente di rotaie analizzava la stringa di query (mi sembrava di ricordarla sovrascrivendo i precedenti valori "b"). Inizia GET "/? A = a & b% 5B% 5D = c & b% 5B% 5D = d & b% 5B% 5D = e" per 127.0.0.1 al 2011-03-10 11:19:40 -0600 Elaborazione da SitesController # indice come Parametri HTML: {"a" => "a", "b" => ["c", "d", "e"]}
Gabe Martin-Dempesy

cosa non va se ci sono hash nidificati? Perché non posso usarlo quando ci sono hash nidificati? Per me, solo l'URL sfugge all'hash nidificato, non dovrebbe esserci alcun problema nell'usarlo nella richiesta http.
Sam,

2
Senza binari: require 'active_support/all'è necessario
Dorian,

Almeno con Rails 5.2 to_querynon gestisce correttamente i valori zero. { a: nil, b: '1'}.to_query == "a=&b=1", ma Rack e CGI entrambi analizzano a=come una stringa vuota, non nil. Non sono sicuro del supporto per altri server, ma con le rotaie dovrebbe essere la stringa di query correttaa&b=1 . Penso che sia sbagliato che Rails non possa produrre una stringa di query correttamente analizzata da sola ...
jsmartt

154

Se si utilizza Ruby 1.9.2 o versioni successive, è possibile utilizzare URI.encode_www_formse non sono necessari array.

Ad esempio (dai documenti di Ruby in 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"

Noterai che i valori di array non sono impostati con nomi di chiavi contenenti []come a cui tutti siamo abituati nelle stringhe di query. La specifica che encode_www_formutilizza è conforme alla definizione di application/x-www-form-urlencodeddati HTML5 .


8
+1, questo è di gran lunga il migliore. Non dipende da alcuna fonte al di fuori di Ruby stesso.
Danyel,

+1 funziona bene con '{: a => "a",: b => {: c => "c",: d => true},: e => []}' esempio
Duke,

1
Non sembra funzionare con ruby ​​2.0: l'hash {:c => "c", :d => true}sembra essere controllato, quindi inviato come stringa.
user208769

1
Era una sezione dello snippet più grande sopra -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
Si noti che ciò ha risultati diversi per i valori dell'array rispetto a Addressable::URIquelli di ActiveSupport Object#to_query.
Matt Huggins,

61

Non è necessario caricare l'ActiveSupport gonfio o rollare il proprio, è possibile utilizzare Rack::Utils.build_querye Rack::Utils.build_nested_query. Ecco un post sul blog che fornisce un buon esempio:

require 'rack'

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

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

Gestisce persino array:

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

O le cose nidificate più difficili:

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

L'esempio nidificato dimostra che non funziona correttamente - quando si avvia, :bè un array di due hash. Si finisce per :bessere una schiera di un hash più grande.
Ed Ruder,

3
@EdRuder non esiste correttamente perché non esiste uno standard accettato. Ciò che mostra è che è molto più vicino del tentativo di chiunque altro, a giudicare dalle altre risposte.
Iain

1
Questo metodo è obsoleto da Rails 2.3.8: apidock.com/rails/Rack/Utils/build_query
davidgoli

8
@davidgoli Erm, non in Rack non è github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140 . Se vuoi usarlo in Rails, sicuramente è semplice come require 'rack'? Deve essere lì, considerando tutti i principali framework Web di Ruby ora costruiti su Rack.
Iain

@EdRuder ActiveSupport to_queryunisce anche i 2 array (v4.2).
Kelvin,

9

Ruba da 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

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


1
Sfortunatamente, questo deos non funziona quando abbiamo un array nidificato all'interno dei parametri (vedi esempio n. 2) ... :(
Julien Genestoux,

2
E non fa alcun re di fuga.
Ernest,

9

Ecco una breve e dolce riga se devi solo supportare semplici stringhe di query chiave / valore 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"

Ecco un altro modo. Per domande semplici.


2
dovresti davvero assicurarti di sfuggire correttamente alle tue chiavi e ai tuoi valori URI. Anche per casi semplici. Ti morderà.
jrochkind,

2

So che questa è una vecchia domanda, ma volevo solo pubblicare questo bit di codice in quanto non riuscivo a trovare un semplice gioiello per fare proprio questo compito per me.

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

Arrotolato come gemma qui: https://github.com/simen/queryparams


1
URI.escape != CGI.escapee per URL vuoi il primo.
Ernest,

2
In realtà no, @Ernest. Quando, ad esempio, incorporando un altro URL come parametro nell'URL (supponiamo che questo sia l'URL di ritorno a cui reindirizzare dopo il login) URI.escape manterrà il '?' e '&' dell'URL incorporato in posizione che interrompe l'URL circostante, mentre CGI.escape li nasconderà correttamente per un periodo successivo come% 3F e% 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

L'approccio migliore è usare Hash.to_params che è quello che funziona bene con le matrici.

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

Senza binari: require 'active_support/all'è necessario
Dorian,

1

Se sei nel contesto di una richiesta Faraday, puoi anche passare l'hash params come secondo argomento e faraday si occupa di farne parte l'URL param corretto:

faraday_instance.get(url, params_hsh)

0

Mi piace usare questo gioiello:

https://rubygems.org/gems/php_http_build_query

Esempio di utilizzo:

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.