루비 : 해시를 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

흠, 이것은 실제로 Json이 아니지만 Ruby Hash입니다 ... 인코딩이 중요한 이유를 잘 모르겠습니다.
Julien Genestoux

lmanners의 답변을 홍보해야합니다. 여기에 많은 훌륭한 답변이 있습니다 (많은 점수를 얻음). ActiveSupport는 이에 대한 표준화 된 지원을 추가하여 대화를 혼란스럽게 만듭니다. 불행히도 lmanner의 답변은 여전히 ​​목록에 묻혀 있습니다.
Noach Magedman

2
@Noach 제 의견으로는, 핵심 클래스를 패치하는 원숭이에 라이브러리를 사용한다고 말하는 대답은 묻혀 있어야합니다. 이러한 패치의 수에 대한 정당성은 기껏해야 흔들 립니다 ( 이 기사 에서 Yehuda Katz의 의견을 살펴보십시오 ). 이것은 훌륭한 예입니다. YMMV, 그러나 저에게는 클래스 메소드가 있거나 Object와 Hash를 열지 않으며 저자가 "우리와 충돌하지 마십시오!"라고 말하지 않는 것이 있습니다. 훨씬 더 좋을 것입니다.
iain

답변:


86

업데이트 : 이 기능은 gem에서 제거되었습니다.

Julien, 당신의 자기 답은 좋은 것입니다, 그리고 나는 그것을 부끄럽게 빌려 왔습니다. 그러나 그것은 예약 된 문자를 제대로 피하지 못하며, 다른 몇 가지 경우가 있습니다.

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

2
부울을 처리하지 않으므로 바람직하지 않습니다. { "a"=> "a & b = b"}. to_params
Bob Aman

3
참고로 불행히도이 동작은 2.3 ( github.com/sporkmonger/addressable/commit/… ) 에서 주소 지정 가능에서 제거되었습니다.
oif_vet

2
@oif_vet 어떤 행동이 제거되었는지 말할 수 있습니까? 주소 지정이 가능한 보석을 사용하여 원래 포스터의 문제를 해결하는 Bob의 제안 방법은 주소 지정 가능 -2.3.2에서 나에게 효과적입니다.
sheldonh 2012 년

1
@sheldonh, 아니오, @oif_vet이 정확합니다. 이 동작을 제거했습니다. 깊게 중첩 된 구조는 더 이상 query_values뮤 테이터 에 대한 입력으로 Addressable에서 지원되지 않습니다 .
Bob Aman

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

방금 시도했는데 당신이 옳은 것 같습니다. 어쩌면 내 진술은 원래 이전 버전의 레일이 쿼리 문자열을 구문 분석 한 방식 때문일 수 있습니다 (이전의 'b'값을 덮어 쓰는 것을 상기하는 것처럼 보였습니다). 2011-03-10 11:19:40 -0600에서 127.0.0.1에 대해 "/? a = a & b % 5B % 5D = c & b % 5B % 5D = d & b % 5B % 5D = e"를 시작했습니다. HTML 매개 변수 : { "a"=> "a", "b"=> [ "c", "d", "e"]}
Gabe Martin-Dempesy

중첩 해시가 있으면 무엇이 잘못됩니까? 중첩 해시가있을 때 이것을 사용할 수없는 이유는 무엇입니까? 나에게 URL은 중첩 된 해시를 이스케이프 처리하므로 http 요청에서 이것을 사용하는 데 아무런 문제가 없습니다.
Sam

2
레일없이 : 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

153

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_formapplication/x-www-form-urlencoded데이터 의 HTML5 정의에 따릅니다 .


8
+1, 이것이 최고입니다. Ruby 자체 외부의 소스에 의존하지 않습니다.
Danyel

+1은 '{: a => "a", : b => {: c => "c", : d => true}, : e => []}'예에서 작동합니다.
Duke

1
루비 2.0에서 작동하지 않는 것 같습니다-해시 {:c => "c", :d => true}가 검사 된 것처럼 보이 므로 문자열로 전송됩니다.
user208769

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.
매트 허긴 스

61

부풀린 ActiveSupport를로드하거나 직접 롤링 할 필요가 없으며 Rack::Utils.build_query및 을 사용할 수 있습니다 Rack::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"}]}

중첩 된 예제는 제대로 작동하지 않음을 보여줍니다. 시작할 때 :b두 개의 해시 배열입니다. 결국 :b하나의 더 큰 해시 배열이됩니다.
Ed Ruder 2013

3
@EdRuder 허용되는 표준이 없기 때문에 제대로 없습니다. 그것이 보여주는 것은 다른 답변으로 판단하여 다른 사람의 시도보다 훨씬 더 가깝다는 것입니다.
iain

1
:이 방법은 레일 2.3.8 이후 사용되지 않습니다 apidock.com/rails/Rack/Utils/build_query
davidgoli

8
@davidgoli Erm, Rack 이 아닌 github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140이 아닙니다 . Rails에서 사용하고 싶다면 반드시 require 'rack'? 모든 주요 Ruby 웹 프레임 워크가 Rack 위에 구축되어 있다는 점을 고려하면 반드시 있어야합니다.
iain

@EdRuder ActiveSupport는 to_query또한 2 개의 배열 (v4.2)을 병합합니다.
Kelvin

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
불행히도, 우리는 매개 변수 안에 중첩 배열이있을 때이 deosn이 작동하지 않습니다 (예 # 2 참조) ... :(
Julien Genestoux

2
그리고 탈출의 왕을하지 않습니다.
어니스트

9

간단한 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"

다른 방법이 있습니다. 간단한 쿼리


2
키와 값을 올바르게 URI 이스케이프하고 있는지 확인해야합니다. 간단한 경우에도 마찬가지입니다. 물린거야
jrochkind

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.escapeURL의 경우 첫 번째 URL을 원합니다.
어니스트

2
사실은 아닙니다, @ 어니스트. 예를 들어 다른 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"

레일없이 : require 'active_support/all'필요
Dorian

1

패러데이 요청과 관련하여 두 번째 인수로 패러시 해시를 전달하면 패러데이가 적절한 매개 변수 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.