web-dev-qa-db-fra.com

quel est le meilleur moyen de convertir une paire valeur/clé formatée JSON en un hachage Ruby avec le symbole en tant que clé?

Je me demande quel est le meilleur moyen de convertir une paire valeur-clé formatée JSON en un hachage Ruby avec le symbole en tant que clé: Exemple: 

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

Y at-il une méthode d'assistance peut faire cela?

95
ez.

en utilisant la gemme json lors de l'analyse de la chaîne json, vous pouvez passer l'option symbolize_names. Voir ici: http://flori.github.com/json/doc/index.html (regardez sous parse)

par exemple:

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

Leventix, merci pour votre réponse.

La méthode Marshal.load (Marshal.dump (h)) possède probablement la plus grande intégrité des différentes méthodes car elle conserve les types de clé d'origine récursivement.

Cela est important dans le cas où vous avez un hachage imbriqué avec une combinaison de clés de chaîne et de symbole et que vous souhaitez conserver ce mélange lors du décodage (par exemple, cela pourrait se produire si votre hachage contient vos propres objets personnalisés en plus de tiers hautement complexes -party objets dont les clés ne peuvent pas être manipulées/converties pour une raison quelconque, comme une contrainte de temps de projet).

Par exemple.:

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

Méthode 1 : JSON.parse - symbolise toutes les clés de manière récursive => Ne conserve pas le mélange d'origine

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

Méthode 2 : ActiveSupport :: JSON.decode - symbolise uniquement les clés de niveau supérieur => Ne conserve pas le mélange d'origine

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

Méthode 3 : Marshal.load - conserve le mélange de chaînes/symboles d'origine dans les clés imbriquées. PARFAIT!

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

À moins d'un inconvénient que je ne sache pas, je penserais que la méthode 3 est la voie à suivre.

À votre santé

19
frank

Il n'y a rien d'intégré pour faire le tour, mais il n'est pas trop difficile d'écrire le code pour le faire en utilisant la gemme JSON. Si vous l'utilisez, il existe une méthode symbolize_keys intégrée à Rails, mais elle ne symbolise pas les clés de manière récursive comme vous le souhaitez.

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

Comme Leventix l'a dit, la gemme JSON ne gère que les chaînes entre guillemets (ce qui est techniquement correct - JSON doit être formaté avec des guillemets). Ce morceau de code va nettoyer cela avant d'essayer de l'analyser.

5
madlep

Méthode récursive:

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
4
Oel Roc

Bien sûr, il existe un json gem , mais cela ne gère que les guillemets doubles.

1
Leventix

Une autre façon de gérer cela consiste à utiliser la sérialisation/désérialisation YAML, qui préserve également le format de la clé:

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

L'avantage de cette approche semble être un format mieux adapté aux services REST ...

0
bert bruynooghe

Le moyen le plus pratique consiste à utiliser le joyau 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]
0
Mario Ruiz