json formatlı bir anahtar-değer çiftini, anahtar olarak sembol içeren ruby ​​hash'e dönüştürmenin en iyi yolu nedir?


104

Bir json formatlı anahtar değer çiftini anahtar olarak sembol içeren ruby ​​hash'e dönüştürmenin en iyi yolunun ne olduğunu merak ediyorum: örnek:

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

Bunu yapabilecek yardımcı bir yöntem var mı?


bunu http://stackoverflow.com/a/43773159/12974354.1 rayları için deneyin
rails_id

Yanıtlar:


256

json dizesini ayrıştırırken json gemini kullanarak, symbolize_names seçeneğinde geçebilirsiniz. Buraya bakın: http://flori.github.com/json/doc/index.html (ayrıştırmanın altına bakın)

Örneğin:

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

4
Ruby 1.9 bu arada bu kitaplığı içerir.
Simon Perepelitsa

bu eskiden değil :symbolize_keysmiydi? bu isim neden değişti?
Lukas

5
@Lukas: symbolize_keysRails bir şeydir.
wyattisimo

: symbolize_names bir Ruby şeydir
fatuhoku

19

Leventix, cevabın için teşekkürler.

Marshal.load (Marshal.dump (h)) , orijinal anahtar türlerini korur çünkü yöntemi, muhtemelen çeşitli yöntemlerin çoğu bütünlüğü vardır yinelemeli .

Bu, dize ve sembol anahtarlarının bir karışımını içeren iç içe geçmiş bir hash'iniz olması ve kod çözme işleminin ardından bu karışımı korumak istemeniz durumunda önemlidir (örneğin, hash'iniz çok karmaşık / iç içe geçmiş üçüncü öğeye ek olarak kendi özel nesnelerinizi içeriyorsa bu olabilir. - Proje süresi kısıtlaması gibi, anahtarlarını herhangi bir nedenle değiştiremeyeceğiniz / dönüştüremeyeceğiniz parti nesneleri).

Örneğin:

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

Yöntem 1 : JSON.parse - tüm anahtarları özyinelemeli olarak sembolize eder => Orijinal karışımı korumaz

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

Yöntem 2 : ActiveSupport :: JSON.decode - yalnızca en üst düzey anahtarları sembolize eder => Orijinal karışımı korumaz

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

Yöntem 3 : Marshal.load - yuvalanmış anahtarlarda orijinal dize / sembol karışımını korur. MÜKEMMEL!

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

Farkında olmadığım bir dezavantaj olmadıkça, Yöntem 3'ün gidilecek yol olduğunu düşünüyorum.

Şerefe


2
Burada diğer tarafın kontrolüne sahip olduğunuzun garantisi yok, bu yüzden JSON biçimlendirmesine bağlı kalmanız gerektiğine inanıyorum. Her iki tarafın tam kontrolüne sahipseniz, Marshal gerçekten iyi bir formattır, ancak genel amaçlı serileştirme için uygun değildir.
chills42

5

Hile yapmak için yerleşik bir şey yoktur, ancak JSON gemini kullanarak yapmak için kodu yazmak çok da zor değildir. Bunu symbolize_keyskullanıyorsanız Rails'de yerleşik bir yöntem vardır, ancak bu, anahtarları ihtiyaç duyduğunuz gibi özyinelemeli olarak sembolize etmez.

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'in dediği gibi, JSON gem yalnızca çift tırnaklı dizeleri işler (ki bu teknik olarak doğrudur - JSON çift tırnak ile biçimlendirilmelidir). Bu kod parçası, ayrıştırmaya çalışmadan önce onu temizleyecektir.


4

Özyinelemeli yöntem:

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

Tabii ki, bir json cevheri var , ancak bu yalnızca çift tırnak işaretlerini işliyor.


Madlep'in aşağıda söylediği gibi - JSON'un geçerli olacağını biliyorsanız ihtiyacınız olan tek şey bu (örneğin, kendiniz yapıyorsunuz!)
edavey

Bu çalışmıyor. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.

2
Bunun nedeni JSON'un sembolleri temsil edememesidir. Bunun Marshal.load(Marshal.dump([:a]))yerine : kullanabilirsiniz .
Leventix

1

Bunu halletmenin başka bir yolu, anahtarın biçimini de koruyan YAML serileştirme / seriyi kaldırma yöntemini kullanmaktır:

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

Bu yaklaşımın faydası, REST hizmetleri için daha uygun bir format gibi görünüyor ...


Hiçbir zaman YAML.load içine kullanıcı girişi get let: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe

@Rafe, 2013'ün bu güvenlik açığının bugün hala düzeltilmediğini mi söylüyorsunuz?
bert bruynooghe

1
Ruby 2.2'den beri semboller GC'lidir. YAML.loadrastgele nesneleri (örneğin önbellek için) serileştirmek içindir. Önerilen YAML.safe_load, bu blog gönderisinden birkaç ay sonra tanıtıldı, bu yüzden doğru olanı kullanma meselesi: github.com/ruby/psych/commit/…
Rafe

0

En kolay yol nice_hash gemini kullanmaktır: 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]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.