วิธีที่ดีที่สุดในการแปลงคู่ค่าคีย์ที่จัดรูปแบบ json เป็นแฮชทับทิมที่มีสัญลักษณ์เป็นคีย์คืออะไร


104

ฉันสงสัยว่าวิธีใดเป็นวิธีที่ดีที่สุดในการแปลงคู่ค่าคีย์ที่จัดรูปแบบ json เป็นแฮชทับทิมที่มีสัญลักษณ์เป็นคีย์: ตัวอย่าง:

{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==> 
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }

มีวิธีช่วยเหลือที่สามารถทำได้หรือไม่?


ลองhttp://stackoverflow.com/a/43773159/1297435ใช้ราง 4.1
rail_id

คำตอบ:


256

ใช้อัญมณี json เมื่อแยกวิเคราะห์สตริง json คุณสามารถส่งผ่านในตัวเลือก symbolize_names ดูที่นี่: http://flori.github.com/json/doc/index.html (ดูภายใต้การแยกวิเคราะห์)

เช่น:

>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"} 

4
Ruby 1.9 รวมถึงไลบรารีนี้ด้วย
Simon Perepelitsa

สิ่งนี้ไม่เคยเป็นมาก่อน:symbolize_keys? ทำไมชื่อนั้นถึงเปลี่ยนไป?
Lukas

5
@Lukas: symbolize_keysเป็นสิ่งที่ Rails
wyattisimo

: symbolize_names เป็นสิ่งที่ทับทิม
fatuhoku

19

Leventix ขอบคุณสำหรับคำตอบ

Marshal.load (Marshal.dump (H))วิธีการอาจจะมีความสมบูรณ์มากที่สุดของวิธีการต่างๆเพราะมันจะเก็บรักษาประเภทหลักเดิมซ้ำ

นี่เป็นสิ่งสำคัญในกรณีที่คุณมีแฮชที่ซ้อนกันซึ่งมีสตริงและคีย์สัญลักษณ์ผสมกันและคุณต้องการเก็บรักษาส่วนผสมนั้นไว้เมื่อถอดรหัส (ตัวอย่างเช่นสิ่งนี้อาจเกิดขึ้นได้หากแฮชของคุณมีอ็อบเจ็กต์ที่กำหนดเองของคุณเองนอกเหนือจากลำดับที่สามที่ซับซ้อน / ซ้อนกันสูง - วัตถุของบุคคลที่มีคีย์ที่คุณไม่สามารถจัดการ / แปลงได้ไม่ว่าด้วยเหตุผลใดก็ตามเช่นข้อ จำกัด ด้านเวลาของโครงการ)

เช่น:

h = {
      :youtube => {
                    :search   => 'daffy',                 # nested symbol key
                    'history' => ['goofy', 'mickey']      # nested string key
                  }
    }

วิธีที่ 1 : JSON.parse - เป็นสัญลักษณ์ของคีย์ทั้งหมดแบบวนซ้ำ => ไม่เก็บการผสมผสานดั้งเดิม

JSON.parse( h.to_json, {:symbolize_names => true} )
  => { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } } 

วิธีที่ 2 : ActiveSupport :: JSON.decode - เป็นสัญลักษณ์ของคีย์ระดับบนสุดเท่านั้น => ไม่เก็บส่วนผสมดั้งเดิม

ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
  => { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }

วิธีที่ 3 : Marshal.load - รักษาการผสมสตริง / สัญลักษณ์ดั้งเดิมในคีย์ที่ซ้อนกัน สมบูรณ์แบบ!

Marshal.load( Marshal.dump(h) )
  => { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }

เว้นแต่จะมีข้อเสียเปรียบที่ฉันไม่รู้ฉันคิดว่าวิธีที่ 3 คือหนทางที่จะไป

ไชโย


2
ไม่มีการรับประกันว่าคุณสามารถควบคุมอีกด้านหนึ่งได้ดังนั้นฉันเชื่อว่าคุณต้องยึดติดกับการจัดรูปแบบ JSON หากคุณสามารถควบคุมทั้งสองด้านได้อย่างสมบูรณ์จอมพลก็เป็นรูปแบบที่ดี แต่ไม่เหมาะสำหรับการทำให้เป็นอนุกรมสำหรับวัตถุประสงค์ทั่วไป
หนาวสั่น 42

5

ไม่มีอะไรในตัวเพื่อทำเคล็ดลับ แต่ก็ไม่ยากเกินไปที่จะเขียนโค้ดเพื่อทำโดยใช้อัญมณี JSON มีsymbolize_keysวิธีการใน Rails หากคุณใช้ แต่นั่นไม่ได้เป็นสัญลักษณ์ของคีย์ซ้ำ ๆ อย่างที่คุณต้องการ

require 'json'

def json_to_sym_hash(json)
  json.gsub!('\'', '"')
  parsed = JSON.parse(json)
  symbolize_keys(parsed)
end

def symbolize_keys(hash)
  hash.inject({}){|new_hash, key_value|
    key, value = key_value
    value = symbolize_keys(value) if value.is_a?(Hash)
    new_hash[key.to_sym] = value
    new_hash
  }
end

ดังที่ Leventix กล่าวไว้อัญมณี JSON จะจัดการเฉพาะสตริงที่ยกมาสองครั้งเท่านั้น (ซึ่งถูกต้องในทางเทคนิค - ควรจัดรูปแบบ JSON ด้วยเครื่องหมายคำพูดคู่) บิตของโค้ดนี้จะทำความสะอาดก่อนที่จะพยายามแยกวิเคราะห์


4

วิธีการเรียกซ้ำ:

require 'json'

def JSON.parse(source, opts = {})
  r = JSON.parser.new(source, opts).parse
  r = keys_to_symbol(r) if opts[:symbolize_names]
  return r
end

def keys_to_symbol(h)
  new_hash = {}
  h.each do |k,v|
    if v.class == String || v.class == Fixnum || v.class == Float
      new_hash[k.to_sym] = v
    elsif v.class == Hash
      new_hash[k.to_sym] = keys_to_symbol(v)
    elsif v.class == Array
      new_hash[k.to_sym] = keys_to_symbol_array(v)
    else
      raise ArgumentError, "Type not supported: #{v.class}"
    end
  end
  return new_hash
end

def keys_to_symbol_array(array)
  new_array = []
  array.each do |i|
    if i.class == Hash
      new_array << keys_to_symbol(i)
    elsif i.class == Array
      new_array << keys_to_symbol_array(i)
    else
      new_array << i
    end
  end
  return new_array
end

1

แน่นอนว่ามีอัญมณี jsonแต่จัดการเฉพาะเครื่องหมายคำพูดคู่


ดังที่ madlep กล่าวไว้ด้านล่างนั่นคือทั้งหมดที่คุณต้องการถ้าคุณรู้ว่า JSON นั้นถูกต้อง (เช่นคุณกำลังทำเอง!)
edavey

วิธีนี้ใช้ไม่ได้ JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.

2
นั่นเป็นเพราะ JSON ไม่สามารถแสดงสัญลักษณ์ได้ คุณสามารถใช้: Marshal.load(Marshal.dump([:a]))แทน
Leventix

1

อีกวิธีหนึ่งในการจัดการสิ่งนี้คือการใช้ YAML serialization / deserialization ซึ่งจะรักษารูปแบบของคีย์ไว้ด้วย:

YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml) 
=> {:test=>{"test"=>{":test"=>5}}}

ประโยชน์ของแนวทางนี้ดูเหมือนว่าจะเป็นรูปแบบที่เหมาะกับบริการ REST มากกว่า ...


อย่าให้ข้อมูลของผู้ใช้เข้าสู่ YAML.load: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe

@Rafe คุณหมายถึงช่องโหว่ของปี 2013 นี้ยังไม่ได้รับการแก้ไขในวันนี้ใช่หรือไม่?
bert bruynooghe

1
สัญลักษณ์เป็น GC ตั้งแต่ Ruby 2.2 YAML.loadหมายถึงการทำให้เป็นอนุกรมวัตถุโดยพลการ (เช่นสำหรับแคช) ข้อเสนอYAML.safe_loadดังกล่าวได้รับการแนะนำไม่กี่เดือนหลังจากโพสต์บล็อกนั้นดังนั้นจึงเป็นเรื่องของการใช้สิ่งที่ถูกต้อง: github.com/ruby/psych/commit/…
Rafe

0

วิธีที่สะดวกที่สุดคือการใช้ nice_hash gem: https://github.com/MarioRuiz/nice_hash

require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"

# on my_hash will have the json as a hash
my_hash = my_str.json

# or you can filter and get what you want
vals = my_str.json(:age, :city)

# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.