Was ist der beste Weg, um ein json-formatiertes Schlüsselwertpaar in Ruby-Hash mit Symbol als Schlüssel umzuwandeln?

102

Ich frage mich, wie man ein json-formatiertes Schlüsselwertpaar am besten in Ruby-Hash mit Symbol als Schlüssel konvertiert: Beispiel:

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

Gibt es eine Hilfsmethode, die dies tun kann?

ez.
quelle
Versuchen Sie dies http://stackoverflow.com/a/43773159/1297435für Rails 4.1
Rails_id

Antworten:

255

Wenn Sie beim Parsen des JSON-Strings den Edelstein json verwenden, können Sie die Option symbolize_names übergeben. Siehe hier: http://flori.github.com/json/doc/index.html (siehe unter Analyse)

z.B:

>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"} 
jai
quelle
4
Ruby 1.9 enthält diese Bibliothek übrigens.
Simon Perepelitsa
War das nicht früher :symbolize_keys? Warum hat sich dieser Name geändert?
Lukas
5
@ Lukas: symbolize_keysist eine Rails-Sache.
Wyattisimo
: symbolize_names ist eine Ruby-Sache
fatuhoku
19

Leventix, vielen Dank für Ihre Antwort.

Die Marshal.load- Methode (Marshal.dump (h)) weist wahrscheinlich die Integrität der verschiedenen Methoden auf, da die ursprünglichen Schlüsseltypen rekursiv beibehalten werden .

Dies ist wichtig, wenn Sie einen verschachtelten Hash mit einer Mischung aus Zeichenfolgen- und Symbolschlüsseln haben und diese Mischung beim Dekodieren beibehalten möchten (dies kann beispielsweise passieren, wenn Ihr Hash neben hochkomplexen / verschachtelten dritten auch Ihre eigenen benutzerdefinierten Objekte enthält -Party-Objekte, deren Schlüssel Sie aus irgendeinem Grund nicht bearbeiten / konvertieren können (z. B. eine Projektzeitbeschränkung).

Z.B:

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

Methode 1 : JSON.parse - symbolisiert alle Schlüssel rekursiv => Bewahrt nicht die ursprüngliche Mischung

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

Methode 2 : ActiveSupport :: JSON.decode - symbolisiert nur Schlüssel der obersten Ebene => Erhält nicht die ursprüngliche Mischung

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

Methode 3 : Marshal.load - behält die ursprüngliche Zeichenfolge / Symbol-Mischung in den verschachtelten Schlüsseln bei. PERFEKT!

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

Wenn es keinen Nachteil gibt, den ich nicht kenne, würde ich denken, dass Methode 3 der richtige Weg ist.

Prost

frank
quelle
2
Hier gibt es keine Garantie dafür, dass Sie die Kontrolle über die andere Seite haben. Ich glaube, Sie müssen sich an die JSON-Formatierung halten. Wenn Sie die volle Kontrolle über beide Seiten haben, ist Marshal zwar ein gutes Format, aber nicht für die allgemeine Serialisierung geeignet.
Chills42
5

Es ist nichts eingebaut, um den Trick auszuführen, aber es ist nicht zu schwierig, den Code dafür mit dem JSON-Gem zu schreiben. symbolize_keysWenn Sie diese verwenden, ist in Rails eine Methode integriert, die jedoch nicht die von Ihnen benötigten Schlüssel rekursiv symbolisiert.

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

Wie Leventix sagte, verarbeitet das JSON-Gem nur Zeichenfolgen mit doppelten Anführungszeichen (was technisch korrekt ist - JSON sollte mit doppelten Anführungszeichen formatiert werden). Dieses Stück Code bereinigt das, bevor versucht wird, es zu analysieren.

Madlep
quelle
4

Rekursive Methode:

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
quelle
1

Natürlich gibt es ein Juwel von json , aber das behandelt nur doppelte Anführungszeichen.

Leventix
quelle
Wie Madlep weiter unten sagt - das ist alles, was Sie brauchen, wenn Sie wissen, dass der JSON gültig ist (z. B. machen Sie es selbst!)
edavey
Das funktioniert nicht. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.
2
Das liegt daran, dass JSON keine Symbole darstellen kann. Sie können Marshal.load(Marshal.dump([:a]))stattdessen verwenden : .
Leventix
1

Eine andere Möglichkeit, dies zu handhaben, ist die Verwendung der YAML-Serialisierung / Deserialisierung, bei der auch das Format des Schlüssels erhalten bleibt:

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

Der Vorteil dieses Ansatzes scheint ein Format zu sein, das besser für REST-Services geeignet ist ...

Bert Bruynooghe
quelle
Nie Benutzereingaben get in YAML.load lassen: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe
@Rafe meinst du, diese Sicherheitslücke von 2013 ist heute noch nicht behoben?
Bert Bruynooghe
1
Symbole werden seit Ruby 2.2 GC'ed. YAML.loadsoll beliebige Objekte serialisieren (zB für Cache). Der Vorschlag YAML.safe_loadwurde einige Monate nach diesem Blog-Beitrag eingeführt, daher ist es wichtig, das Richtige zu verwenden: github.com/ruby/psych/commit/…
Rafe
0

Am bequemsten ist es, das Juwel nice_hash zu verwenden: 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
quelle