apa cara terbaik untuk mengonversi pasangan nilai kunci berformat json menjadi hash ruby ​​dengan simbol sebagai kuncinya?

103

Saya bertanya-tanya apa cara terbaik untuk mengonversi pasangan nilai kunci berformat json menjadi hash ruby ​​dengan simbol sebagai kunci: contoh:

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

Apakah ada metode pembantu yang bisa melakukan ini?

ez.
sumber
coba ini http://stackoverflow.com/a/43773159/1297435untuk rel 4.1
rails_id

Jawaban:

256

menggunakan permata json saat mem-parsing string json Anda dapat meneruskan opsi symbolize_names. Lihat di sini: http://flori.github.com/json/doc/index.html (lihat di bawah parse)

misalnya:

>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"} 
jai
sumber
4
Omong-omong, Ruby 1.9 menyertakan pustaka ini.
Simon Perepelitsa
bukankah ini dulu :symbolize_keys? kenapa nama itu berubah?
Lukas
5
@ Lukas: symbolize_keysadalah hal Rails.
wyattisimo
: symbolize_names adalah Ruby
fatuhoku
19

Leventix, terima kasih atas jawaban Anda.

Metode Marshal.load (Marshal.dump (h)) mungkin memiliki integritas paling tinggi dari berbagai metode karena metode ini mempertahankan jenis kunci asli secara rekursif .

Ini penting jika Anda memiliki hash bersarang dengan campuran string dan kunci simbol dan Anda ingin mempertahankan campuran tersebut setelah dekode (misalnya, ini bisa terjadi jika hash Anda berisi objek khusus Anda sendiri selain sepertiga yang sangat kompleks / bersarang) objek -party yang kuncinya tidak dapat Anda manipulasi / konversi karena alasan apa pun, seperti batasan waktu proyek).

Misalnya:

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

Metode 1 : JSON.parse - melambangkan semua kunci secara rekursif => Tidak menyimpan campuran asli

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

Metode 2 : ActiveSupport :: JSON.decode - hanya melambangkan kunci tingkat atas => Tidak menyimpan campuran asli

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

Metode 3 : Marshal.load - mempertahankan campuran string / simbol asli dalam kunci bersarang. SEMPURNA!

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

Kecuali jika ada kelemahan yang tidak saya sadari, saya akan berpikir Metode 3 adalah cara yang tepat.

Bersulang

jujur
sumber
2
Tidak ada jaminan di sini bahwa Anda memiliki kendali atas sisi lain, jadi saya yakin Anda harus tetap berpegang pada format JSON. Jika Anda memiliki kendali penuh atas kedua sisi, Marsekal memang format yang bagus, tetapi tidak cocok untuk serialisasi tujuan umum.
kedinginan 42
5

Tidak ada bawaan untuk melakukan trik ini, tetapi tidak terlalu sulit untuk menulis kode untuk melakukannya menggunakan permata JSON. Ada sebuahsymbolize_keys metode yang dibangun ke dalam Rails jika Anda menggunakannya, tetapi itu tidak melambangkan kunci secara rekursif seperti yang Anda butuhkan.

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

Seperti yang dikatakan Leventix, permata JSON hanya menangani string yang dikutip ganda (yang secara teknis benar - JSON harus diformat dengan tanda kutip ganda). Sedikit kode ini akan membersihkannya sebelum mencoba menguraikannya.

madlep
sumber
4

Metode rekursif:

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
Oel Roc
sumber
1

Tentu saja, ada permata json , tapi itu hanya menangani tanda kutip ganda.

Leventix
sumber
Seperti yang dikatakan madlep di bawah - hanya itu yang Anda butuhkan jika Anda tahu bahwa JSON akan valid (misalnya Anda membuatnya sendiri!)
edavey
Ini tidak berhasil. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.
2
Itu karena JSON tidak dapat mewakili simbol. Anda dapat menggunakan: Marshal.load(Marshal.dump([:a]))sebagai gantinya.
Leventix
1

Cara lain untuk menanganinya adalah dengan menggunakan serialisasi / deserialisasi YAML, yang juga mempertahankan format kunci:

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

Manfaat dari pendekatan ini, sepertinya format yang lebih cocok untuk layanan REST ...

bert bruynooghe
sumber
Jangan biarkan masukan pengguna masuk ke YAML. Muat: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe
@Rafe Maksud Anda lubang keamanan tahun 2013 ini masih belum diperbaiki hari ini?
bert bruynooghe
1
Simbol di-GC sejak Ruby 2.2. YAML.loaddimaksudkan untuk menserialisasikan objek sewenang-wenang (misalnya untuk cache). Usulan YAML.safe_loadtelah diperkenalkan beberapa bulan setelah posting blog itu, jadi masalah menggunakan hal yang benar: github.com/ruby/psych/commit/…
Rafe
0

Cara yang paling nyaman adalah dengan menggunakan permata nice_hash: 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]
Mario Ruiz
sumber