Ruby: จะเปลี่ยนแฮชเป็นพารามิเตอร์ 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 params ... บางทีคุณอาจต้องการการเข้ารหัสที่แตกต่างกันใช่ไหม
CookieOfFortune

ฮัมเพลงนี่ไม่ใช่ Json แต่เป็น Ruby Hash ... ไม่แน่ใจว่าฉันเข้าใจว่าทำไมการเข้ารหัสจึงมีความสำคัญ
Julien Genestoux

คำตอบโดย lmanners ควรได้รับการส่งเสริม มีหลายคำตอบที่ยอดเยี่ยมของคุณเองที่นี่ (หลายคนที่มีคะแนนสูง) แต่ ActiveSupport ได้เพิ่มการสนับสนุนที่เป็นมาตรฐานสำหรับสิ่งนี้ทำให้เรนเดอร์บทสนทนา น่าเสียดายที่คำตอบของ lmanner ยังคงฝังอยู่ในรายการ
Noach Magedman

2
@ ไม่เห็นด้วยกับความคิดของฉันคำตอบใด ๆ ที่บอกว่าต้องพึ่งพาห้องสมุดที่มีการฝังชั้นเรียนหลักของลิงเป็นอย่างมาก ข้ออ้างสำหรับแพทช์เหล่านั้นเป็นจำนวนมากนั้นสั่นคลอนที่สุด (ดูที่ความคิดเห็นของ Yehuda Katz ในบทความนี้ ) นี่เป็นตัวอย่างที่ยอดเยี่ยม YMMV แต่สำหรับฉันมีบางอย่างที่มีวิธีการเรียนหรือที่ไม่ได้เปิด Object และ Hash และที่ผู้เขียนจะไม่พูดว่า "แค่อย่าปะทะกับเรา!" จะดีกว่านี้มาก
Iain

คำตอบ:


86

อัปเดต:ฟังก์ชันนี้ถูกลบออกจากอัญมณี

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
มันไม่ได้จัดการกับ booleans และสิ่งนี้เป็นสิ่งที่ไม่พึงประสงค์อย่างชัดเจน: {"a" => "a & b = b"}. to_params
Bob Aman

3
FYI น่าเสียดายที่พฤติกรรมนี้ถูกลบออกจาก Addressable ตั้งแต่ 2.3 ( github.com/sporkmonger/addressable/commit/ … )
oif_vet

2
@oif_vet คุณพูดได้ไหมว่าพฤติกรรมอะไรถูกลบไปแล้ว? การประเมินที่แนะนำโดยบ๊อบของการใช้อัญมณีที่สามารถระบุตำแหน่งได้เพื่อแก้ปัญหาของโปสเตอร์ต้นฉบับนั้นใช้งานได้สำหรับฉันเมื่อเทียบกับ 2.3.2
sheldonh

1
@sheldonh ไม่ @oif_vet ถูกต้อง ฉันลบพฤติกรรมนี้ โครงสร้างที่ซ้อนกันเชิงลึกไม่ได้รับการสนับสนุนใน Addressable อีกต่อไปเป็นอินพุทของquery_valuesMutator
Bob Aman

269

สำหรับขั้นพื้นฐาน hashes ไม่ใช่ซ้อนกัน 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' ก่อนหน้า) เริ่ม GET "/? a = a & b% 5B% 5D = c & b% 5B% 5D = d & b% 5B% 5D = e" สำหรับ 127.0.0.1 ที่ 2011-03-10 11:19:40 -0600 ประมวลผลโดยดัชนี SitesController # เป็น พารามิเตอร์ HTML: {"a" => "a", "b" => ["c", "d", "e"]}}
Gabe Martin-Dempesy

เกิดอะไรขึ้นถ้ามีแฮชซ้อนกัน ทำไมฉันไม่สามารถใช้สิ่งนี้เมื่อมีแฮชซ้อนกันอยู่ สำหรับฉันแล้ว url จะหนีแฮชที่ซ้อนกันอยู่ไม่ควรมีปัญหาในการใช้สิ่งนี้ในคำขอ http
Sam

2
ไม่มี Rails: 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):

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_formใช้เป็นไปตามข้อกำหนดของapplication/x-www-form-urlencodedข้อมูลHTML5


8
+1 นี่คือสิ่งที่ดีที่สุด มันไม่ได้ขึ้นอยู่กับแหล่งข้อมูลใด ๆ นอกทับทิมเอง
Danyel

+1 ทำงานได้ดีกับ '{: a => "a",: b => {: c => "c",: d => จริง},: e => []}' ตัวอย่าง
Duke

1
ดูเหมือนจะไม่ทำงานกับ ruby ​​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และ Object#to_queryActiveSupport
Matt Huggins

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

3
@ErRuder ไม่ถูกต้องเพราะไม่มีมาตรฐานที่ยอมรับได้ สิ่งที่แสดงให้เห็นคือมันใกล้กว่าความพยายามของผู้อื่นโดยตัดสินจากคำตอบอื่น ๆ
Iain

1
วิธีนี้เลิกใช้แล้วตั้งแต่ Rails 2.3.8: apidock.com/rails/Rack/Utils/build_query
davidgoli

8
@davidgoli เอ่อไม่ได้อยู่ในตู้แร็คก็ไม่github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140 หากคุณต้องการใช้ใน Rails แน่นอนว่ามันง่ายเหมือนrequire 'rack'? มันจะต้องอยู่ที่นั่นเพราะเฟรมเวิร์กเว็บทับทิมที่สำคัญทั้งหมดถูกสร้างขึ้นบนสุดของชั้นวางตอนนี้
Iain

@EdRuder ActiveSupport to_queryยังผสาน 2 อาร์เรย์ (v4.2)
เคลวิน

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
ขออภัย deos นี้ไม่ทำงานเมื่อเรามีอาร์เรย์ที่ซ้อนกันอยู่ใน params (ดูตัวอย่างที่ 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.escapeและสำหรับ URL ที่คุณต้องการอันแรก
เออร์เนสต์

2
ไม่จริง @Ernest เมื่อเช่นฝัง 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"

ไม่มี Rails: require 'active_support/all'จำเป็นต้องใช้
Dorian

1

หากคุณอยู่ในบริบทของคำขอฟาราเดย์คุณสามารถส่งแฮชพารามิเตอร์ได้เนื่องจากอาร์กิวเมนต์ที่สองและฟาราเดย์ดูแลการทำ URL พารามิเตอร์ที่เหมาะสม:

faraday_instance.get(url, params_hsh)


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.