qual è il modo migliore per convertire una coppia di valori chiave formattata json in un hash rubino con il simbolo come chiave?


103

Mi chiedo qual è il modo migliore per convertire una coppia di valori chiave in formato json in un hash rubino con il simbolo come chiave: esempio:

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

Esiste un metodo di supporto in grado di farlo?


prova questo http://stackoverflow.com/a/43773159/1297435per rails 4.1
rails_id

Risposte:


256

usando la gemma json durante l'analisi della stringa json puoi passare nell'opzione symbolize_names. Vedi qui: http://flori.github.com/json/doc/index.html (guarda sotto analisi)

per esempio:

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

4
Ruby 1.9 include questa libreria, tra l'altro.
Simon Perepelitsa

non era questo :symbolize_keys? perché quel nome è cambiato?
Lukas

5
@ Lukas: symbolize_keysè una cosa di Rails.
wyattisimo

: symbolize_names è una cosa di Ruby però
fatuhoku

19

Leventix, grazie per la tua risposta.

Il metodo Marshal.load (Marshal.dump (h)) ha probabilmente la maggiore integrità dei vari metodi perché conserva i tipi di chiave originali in modo ricorsivo .

Questoèimportante nel caso tu abbia un hash nidificato con un mix di stringhe e chiavi di simboli e desideri preservare quel mix dopo la decodifica (ad esempio, ciò potrebbe accadere se il tuo hash contiene i tuoi oggetti personalizzati oltre a terzi altamente complessi / nidificati -oggetto di parti le cui chiavi non è possibile manipolare / convertire per qualsiasi motivo, come un vincolo di tempo del progetto).

Per esempio:

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

Metodo 1 : JSON.parse - simbolizza tutte le chiavi in ​​modo ricorsivo => Non conserva il mix originale

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

Metodo 2 : ActiveSupport :: JSON.decode - simbolizza solo le chiavi di primo livello => Non conserva il mix originale

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

Metodo 3 : Marshal.load - conserva la combinazione stringa / simbolo originale nelle chiavi nidificate. PERFETTO!

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

A meno che non ci sia un inconveniente di cui non sono a conoscenza, penso che il Metodo 3 sia la strada da percorrere.

Saluti


2
Non c'è alcuna garanzia qui che tu abbia il controllo dell'altro lato, quindi credo che tu debba attenersi alla formattazione JSON. Se hai il pieno controllo di entrambi i lati, Marshal è davvero un buon formato, ma non è adatto per la serializzazione generica.
brividi42

5

Non c'è nulla di integrato per fare il trucco, ma non è troppo difficile scrivere il codice per farlo usando la gemma JSON. C'è un symbolize_keysmetodo integrato in Rails se lo usi, ma questo non simboleggia le chiavi in ​​modo ricorsivo come ti serve.

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

Come ha detto Leventix, la gemma JSON gestisce solo stringhe tra virgolette doppie (il che è tecnicamente corretto: JSON dovrebbe essere formattato con virgolette doppie). Questo bit di codice lo ripulirà prima di provare ad analizzarlo.


4

Metodo ricorsivo:

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

Ovviamente c'è una gemma json , ma che gestisce solo virgolette doppie.


Come dice madlep di seguito, è tutto ciò di cui hai bisogno se sai che il JSON sarà valido (ad es. Lo stai facendo da solo!)
edavey

Questo non funziona. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.

2
Questo perché JSON non può rappresentare i simboli. Puoi usare: Marshal.load(Marshal.dump([:a]))invece.
Leventix

1

Un altro modo per gestirlo è utilizzare la serializzazione / deserializzazione YAML, che conserva anche il formato della chiave:

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

Il vantaggio di questo approccio sembra un formato più adatto per i servizi REST ...


Non lasciare mai che l'input dell'utente entri in YAML.load: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe

@Rafe, vuoi dire che questa falla di sicurezza del 2013 non è stata ancora risolta oggi?
bert bruynooghe

1
I simboli sono scritti in GC a partire da Ruby 2.2. YAML.loadha lo scopo di serializzare oggetti arbitrari (ad esempio per la cache). La proposta YAML.safe_loadè stata introdotta pochi mesi dopo quel post sul blog, quindi si tratta di usare la cosa giusta: github.com/ruby/psych/commit/…
Rafe

0

Il modo più conveniente è usare la gemma 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]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.