Ruby: How to turn a hash into HTTP parameters?
Solution 1
Update: This functionality was removed from the gem.
Julien, your self-answer is a good one, and I've shameless borrowed from it, but it doesn't properly escape reserved characters, and there are a few other edge cases where it breaks down.
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]"
The gem is 'addressable'
gem install addressable
Solution 2
For basic, non-nested hashes, Rails/ActiveSupport has 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
Solution 3
If you are using Ruby 1.9.2 or later, you can use URI.encode_www_form
if you don't need arrays.
E.g. (from the Ruby docs 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"
You'll notice that array values are not set with key names containing []
like we've all become used to in query strings. The spec that encode_www_form
uses is in accordance with the HTML5 definition of application/x-www-form-urlencoded
data.
Solution 4
No need to load up the bloated ActiveSupport or roll your own, you can use Rack::Utils.build_query
and Rack::Utils.build_nested_query
. Here's a blog post that gives a good example:
require 'rack'
Rack::Utils.build_query(
authorization_token: "foo",
access_level: "moderator",
previous: "index"
)
# => "authorization_token=foo&access_level=moderator&previous=index"
It even handles arrays:
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"]}
Or the more difficult nested stuff:
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"}]}
Solution 5
Here's a short and sweet one liner if you only need to support simple ASCII key/value query strings:
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"
Related videos on Youtube
Comments
-
Julien Genestoux over 3 years
That is pretty easy with a plain hash like
{:a => "a", :b => "b"}
which would translate into
"a=a&b=b"
But what do you do with something more complex like
{:a => "a", :b => ["c", "d", "e"]}
which should translate into
"a=a&b[0]=c&b[1]=d&b[2]=e"
Or even worse, (what to do) with something like:
{:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]
Thanks for the much appreciated help with that!
-
CookieOfFortune about 15 yearsIt sounds like you want to convert JSON into HTTP params... perhaps you need a different encoding?
-
Julien Genestoux about 15 yearsHum, this is actually not Json, but a Ruby Hash... not sure I understand why encoding matters here.
-
Noach Magedman about 11 yearsThe answer by lmanners ought to be promoted. There are a lot of great roll-your-own answers here (many with high scores) but ActiveSupport has since added standardized support for this, rendering the conversation moot. Unfortunately, lmanner's answer is still buried down the list.
-
ian about 11 years@Noach in my opinion, any answer that says to rely on a library that heavily monkey patches core classes should remain buried. The justification for a huge number of those patches is shaky at best (take a look at Yehuda Katz's comments in this article), this being an excellent example. YMMV, but for me, something with a class method or that doesn't open Object and Hash, and where the authors wouldn't say "just don't clash with us!" would be much, much better.
-
-
Julien Genestoux about 15 yearsUnfortunately, this deosn't work when we have a nested Array inside the params (see example #2)... :(
-
Julien Genestoux about 15 yearsThx! What are the edge cases where my solution breaks? so I can do add it to the specs?
-
Bob Aman about 15 yearsIt doesn't handle booleans, and this is clearly undesirable: {"a" => "a&b=b"}.to_params
-
tokland over 13 yearsWhy do you say it's broken? the output you showed is ok, isn't?
-
Gabe Martin-Dempesy over 13 yearsI just tried it and you seem to be right. Maybe my statement was originally due to the way an earlier version of rails parsed the query string (I seemed to recall it overwriting the previous 'b' values). Started GET "/?a=a&b%5B%5D=c&b%5B%5D=d&b%5B%5D=e" for 127.0.0.1 at 2011-03-10 11:19:40 -0600 Processing by SitesController#index as HTML Parameters: {"a"=>"a", "b"=>["c", "d", "e"]}
-
Ernest about 12 years
URI.escape != CGI.escape
and for URL you want the first one. -
Ernest about 12 yearsAnd does not do any king of escaping.
-
svale about 12 yearsActually not, @Ernest. When e.g. embedding another url as a parameter to your url (lets say this is the return url to be redirected to after login) URI.escape will keep the '?' and '&' of the embedded url in place breaking the surrounding url, while CGI.escape will correctly tuck them away for later as %3F and %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"
-
oif_vet almost 12 yearsFYI, unfortunately this behavior has been removed from Addressable as of 2.3 (github.com/sporkmonger/addressable/commit/…)
-
sheldonh over 11 years@oif_vet Could you say what behaviour has been removed? Bob's suggested appraoch of using the addressable gem to solve the original poster's problem works for me as of addressable-2.3.2.
-
Bob Aman over 11 years@sheldonh, no, @oif_vet is correct. I removed this behavior. Deeply nested structures are no longer supported in Addressable as inputs to the
query_values
mutator. -
Ed Ruder over 11 yearsYour nested example demonstrates that it doesn't work properly--when you start,
:b
is an array of two hashes. You end up with:b
being an array of one bigger hash. -
ian over 11 years@EdRuder there's no properly because there's no accepted standard. What it does show is that it's a lot closer than anyone else's attempt, judging by the other answers.
-
davidgoli about 11 yearsThis method is deprecated since Rails 2.3.8: apidock.com/rails/Rack/Utils/build_query
-
ian about 11 years@davidgoli Erm, not in Rack it's not github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140. If you want to use it in Rails, surely it's as simple as
require 'rack'
? It must be there, considering all the major Ruby web frameworks are built on top of Rack now. -
Danyel almost 11 years+1, this is by far the best. It doesn't depend on any sources outside of Ruby itself.
-
Duke almost 11 years+1 works fine with '{:a => "a", :b => {:c => "c", :d => true}, :e => []}' example
-
user208769 over 10 yearsDoes not seem to work with ruby 2.0 - the hash
{:c => "c", :d => true}
appears to be inspected, so sent through as a string. -
Bo Jeanes over 10 years@user208769, I just tried in Ruby 2.0 and it seems to work fine.
$ ruby -ruri -e 'puts RUBY_VERSION; puts URI.encode_www_form({:c => "c", :d => true})' # outputs 2.0.0 c=c&d=true
-
user208769 over 10 yearsIt was a section of the larger snippet above -
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&
-
Matt Huggins over 10 yearsNote that this has different results for array values than both
Addressable::URI
and ActiveSupport'sObject#to_query
. -
jrochkind about 10 yearsyou really ought to make sure you are properly URI-escaping your keys and values though. Even for simple cases. It'll bite you.
-
Kelvin about 9 years@EdRuder ActiveSupport's
to_query
also merges the 2 arrays (v4.2). -
Sam over 8 yearswhat goes wrong if there are nested hashes? Why I can't use this when there are nested hashes? To me, it just url escapes the nested hash, there should be no problem using this in http request.
-
puchu over 7 yearsIt turns space into "+". This is only for "x-www-form-urlencoded" data.
-
Dorian over 6 yearsWithout Rails:
require 'active_support/all'
is needed -
Dorian over 6 yearsWithout Rails:
require 'active_support/all'
is needed -
jsmartt about 4 yearsAt least with Rails 5.2
to_query
does not handle nil values properly.{ a: nil, b: '1'}.to_query == "a=&b=1"
, but Rack and CGI both parsea=
as an empty string, notnil
. I'm not sure about support for other servers, but with rails, the correct query string should bea&b=1
. I think it's wrong that Rails can't produce a query string that's correctly parsed by itself...