Quelle est la meilleure façon de convertir une paire valeur / clé formatée json en hachage ruby ​​avec le symbole comme clé?

104

Je me demande quel est le meilleur moyen de convertir une paire valeur / clé au format json en hachage rubis avec un symbole comme clé: exemple:

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

Existe-t-il une méthode d'assistance qui peut faire cela?

ez.
la source
essayez ceci http://stackoverflow.com/a/43773159/1297435pour les rails 4.1
rails_id

Réponses:

256

en utilisant le gem 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"} 
I have
la source
4
Ruby 1.9 inclut cette bibliothèque, d'ailleurs.
Simon Perepelitsa
est-ce que ce n'était pas le cas :symbolize_keys? pourquoi ce nom a-t-il changé?
Lukas
5
@Lukas: symbolize_keysc'est un truc Rails.
wyattisimo
: symbolize_names est une chose Ruby cependant
fatuhoku
19

Leventix, merci pour votre réponse.

La méthode Marshal.load (Marshal.dump (h)) a probablement le plus d'intégrité des différentes méthodes car elle préserve les types de clé d'origine de manière récursive .

Ceci est important dans le cas où vous avez un hachage imbriqué avec un mélange de clés de chaîne et de symbole et que vous souhaitez conserver ce mélange lors du décodage (par exemple, cela peut se produire si votre hachage contient vos propres objets personnalisés en plus d'un tiers très complexe / imbriqué -party objets dont vous ne pouvez pas manipuler / convertir les clés 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 - préserve le mélange chaîne / symbole d'origine dans les clés imbriquées. PARFAIT!

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

À moins qu'il y ait un inconvénient dont je ne suis pas conscient, je pense que la méthode 3 est la voie à suivre.

À votre santé

franc
la source
2
Il n'y a aucune garantie ici que vous avez le contrôle de l'autre côté, donc je pense que vous devez vous en tenir au formatage JSON. Si vous avez un contrôle total des deux côtés, alors Marshal est en effet un bon format, mais il ne convient pas à la sérialisation à usage général.
frissons42
5

Il n'y a rien de intégré pour faire le tour, mais il n'est pas trop difficile d'écrire le code pour le faire en utilisant le gem JSON. Il existe une symbolize_keysméthode intégrée à Rails si vous l'utilisez, mais qui ne symbolise pas les clés de manière récursive comme vous en avez besoin.

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 le dit Leventix, le gem JSON ne gère que les chaînes entre guillemets (ce qui est techniquement correct - JSON doit être formaté avec des guillemets doubles). Ce morceau de code nettoiera cela avant d'essayer de l'analyser.

madlep
la source
4

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
Oel Roc
la source
1

Bien sûr, il existe une gemme json , mais qui ne gère que les guillemets doubles.

Leventix
la source
Comme le dit madlep ci-dessous - c'est tout ce dont vous avez besoin si vous savez que le JSON sera valide (par exemple, vous le faites vous-même!)
edavey
Cela ne marche pas. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.
2
C'est parce que JSON ne peut pas représenter de symboles. Vous pouvez utiliser: à la Marshal.load(Marshal.dump([:a]))place.
Leventix du
1

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}}}

Bénéficier de cette approche, cela semble être un format plus adapté aux services REST ...

Bert Bruynooghe
la source
Ne laissez jamais l'entrée utilisateur entrer dans YAML.load: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe
@Rafe voulez-vous dire que cette faille de sécurité de 2013 n'est toujours pas corrigée aujourd'hui?
bert bruynooghe
1
Les symboles sont GC depuis Ruby 2.2. YAML.loadest destiné à sérialiser des objets arbitraires (par exemple pour le cache). La proposition YAML.safe_loada été introduite quelques mois après cet article de blog, il s'agit donc d'utiliser la bonne chose: github.com/ruby/psych/commit/…
Rafe
0

Le moyen le plus pratique consiste à utiliser la gemme 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]
Mario Ruiz
la source