¿Cuál es la mejor manera de convertir un par clave-valor con formato json a ruby ​​hash con el símbolo como clave?


103

Me pregunto cuál es la mejor manera de convertir un par de valores clave con formato json a hash ruby ​​con el símbolo como clave: ejemplo:

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

¿Hay algún método auxiliar que pueda hacer esto?


pruebe esto http://stackoverflow.com/a/43773159/1297435para rails 4.1
rails_id

Respuestas:


256

usando la gema json al analizar la cadena json, puede pasar la opción symbolize_names. Ver aquí: http://flori.github.com/json/doc/index.html (busque debajo de parse)

p.ej:

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

4
Ruby 1.9 incluye esta biblioteca, por cierto.
Simon Perepelitsa

¿No solía ser esto :symbolize_keys? ¿Por qué cambió ese nombre?
Lukas

5
@Lukas: symbolize_keyses cosa de Rails.
wyattisimo

Sin embargo: symbolize_names es una cosa de Ruby
fatuhoku

19

Leventix, gracias por tu respuesta.

El método Marshal.load (Marshal.dump (h)) probablemente tiene la mayor integridad de los varios métodos porque conserva los tipos de clave originales de forma recursiva .

Esto es importante en caso de que tenga un hash anidado con una combinación de claves de cadena y símbolo y desee conservar esa combinación al decodificar (por ejemplo, esto podría suceder si su hash contiene sus propios objetos personalizados además de un tercero altamente complejo / anidado -objetos de fiesta cuyas claves no puede manipular / convertir por cualquier motivo, como una restricción de tiempo del proyecto).

P.ej:

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

Método 1 : JSON.parse: simboliza todas las claves de forma recursiva => No conserva la mezcla original

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

Método 2 : ActiveSupport :: JSON.decode - simboliza solo claves de nivel superior => No conserva la mezcla original

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

Método 3 : Marshal.load: conserva la combinación original de cadena / símbolo en las claves anidadas. ¡PERFECTO!

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

A menos que haya un inconveniente que desconozco, creo que el Método 3 es el camino a seguir.

Salud


2
Aquí no hay garantía de que tengas el control del otro lado, así que creo que debes ceñirte al formato JSON. Si tiene el control total de ambos lados, Marshal es un buen formato, pero no es adecuado para la serialización de propósito general.
escalofríos 42

5

No hay nada integrado para hacer el truco, pero no es demasiado difícil escribir el código para hacerlo usando la gema JSON. Hay un symbolize_keysmétodo integrado en Rails si lo está utilizando, pero eso no simboliza las teclas de forma recursiva como necesita.

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

Como dijo Leventix, la gema JSON solo maneja cadenas entre comillas dobles (lo cual es técnicamente correcto; JSON debe formatearse con comillas dobles). Este fragmento de código lo limpiará antes de intentar analizarlo.


4

Método recursivo:

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

Por supuesto, hay una gema json , pero solo maneja comillas dobles.


Como dice madlep a continuación, eso es todo lo que necesita si sabe que el JSON será válido (por ejemplo, ¡lo está haciendo usted mismo!)
edavey

Esto no funciona. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.

2
Eso es porque JSON no puede representar símbolos. Puede utilizar: en su Marshal.load(Marshal.dump([:a]))lugar.
Leventix

1

Otra forma de manejar esto es usar la serialización / deserialización YAML, que también conserva el formato de la clave:

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

Benefíciese de este enfoque, parece un formato que se adapta mejor a los servicios REST ...


Nunca permita que la entrada del usuario ingrese a YAML.load: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe

@Rafe, ¿quieres decir que este agujero de seguridad de 2013 todavía no se ha solucionado hoy?
bert bruynooghe

1
Los símbolos están clasificados como GC desde Ruby 2.2. YAML.loadestá destinado a serializar objetos arbitrarios (por ejemplo, para caché). La propuesta YAML.safe_loadse presentó unos meses después de esa publicación del blog, por lo que es una cuestión de usar lo correcto: github.com/ruby/psych/commit/…
Rafe

0

La forma más conveniente es usar la gema 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]
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.