Considérez le code suivant:
hash1 = {"one" => 1, "two" => 2, "three" => 3}
hash2 = hash1.reduce({}){ |h, (k,v)| h.merge(k => hash1) }
hash3 = hash2.reduce({}){ |h, (k,v)| h.merge(k => hash2) }
hash4 = hash3.reduce({}){ |h, (k,v)| h.merge(k => hash3) }
hash4 est un hachage `` imbriqué '', c'est-à-dire un hachage avec des clés de chaîne et des valeurs de hachage `` imbriquées '' similaires.
La méthode 'symbolize_keys' pour Hash dans Rails nous permet de convertir facilement les clés de chaîne en symboles. Mais je cherche un moyen élégant pour convertir toutes les clés (primaire touches plus les touches de tous les hachages dans hash4) aux symboles.
Le point est de me sauver de ma ( imo ) solution laide:
class Hash
def symbolize_keys_and_hash_values
symbolize_keys.reduce({}) do |h, (k,v)|
new_val = v.is_a?(Hash) ? v.symbolize_keys_and_hash_values : v
h.merge({k => new_val})
end
end
end
hash4.symbolize_keys_and_hash_values #=> desired result
Pour info: la configuration est Rails 3.2.17 et Ruby 2.1.1
Mise à jour:
La réponse est hash4.deep_symbolize_keys
Pour Rails <= 5.0
La réponse est JSON.parse(JSON[hash4], symbolize_names: true)
pour Rails> 5
Il y a quelques façons de le faire
Il y a une méthode deep_symbolize_keys
Dans Rails
hash.deep_symbolize_keys!
Comme mentionné par @chrisgeeq, il existe une méthode deep_transform_keys
disponible à partir de Rails 4.
hash.deep_transform_keys(&:to_sym)
Il existe également une version bang !
Pour remplacer l'objet existant.
Il existe une autre méthode appelée with_indifferent_access
. Cela vous permet d'accéder à un hachage avec une chaîne ou un symbole comme la façon dont params
sont dans le contrôleur. Cette méthode n'a pas d'équivalent bang.
hash = hash.with_indifferent_access
Le dernier utilise JSON.parse
. Personnellement, je n'aime pas cela parce que vous faites 2 transformations - hachage en json puis json en hachage.
JSON.parse(JSON[h], symbolize_names: true)
MISE À JOUR:
16/01/19 - ajoutez plus d'options et notez la dépréciation de deep_symbolize_keys
19/04/12 - supprimer la note obsolète. seule l'implémentation utilisée dans la méthode est obsolète, pas la méthode elle-même.
Vous ne pouvez pas utiliser cette méthode pour les paramètres ou toute autre instance de ActionController::Parameters
plus, car deep_symbolize_keys
la méthode est déconseillée dans Rails 5.0+ pour des raisons de sécurité et sera supprimée dans Rails 5.1+ as ActionController::Parameters
n'hérite plus de Hash
Donc, cette approche par @Uri Agassi semble être l'universel one.
JSON.parse(JSON[h], symbolize_names: true)
Cependant, Rails Hash object l'a toujours.
Les options sont donc:
si vous n'utilisez pas Rails ou ne vous en souciez pas:
JSON.parse(JSON[h], symbolize_names: true)
avec Rails et ActionController :: Parameters:
params.to_unsafe_h.deep_symbolize_keys
avec Rails et Hash ordinaire
h.deep_symbolize_keys
Dans Rails vous pouvez créer la classe HashWithIndifferentAccess. Créez une instance de cette classe en passant votre hachage à son constructeur, puis accédez-y avec des clés qui sont des symboles ou des chaînes (comme les paramètres des actions du contrôleur):
hash = {'a' => {'b' => [{c: 3}]}}
hash = hash.with_indifferent_access
# equal to:
# hash = ActiveSupport::HashWithIndifferentAccess.new(hash)
hash[:a][:b][0][:c]
=> 3
Je peux suggérer quelque chose comme ceci:
class Object
def deep_symbolize_keys
self
end
end
class Hash
def deep_symbolize_keys
symbolize_keys.tap { |h| h.each { |k, v| h[k] = v.deep_symbolize_keys } }
end
end
{'a'=>1, 'b'=>{'c'=>{'d'=>'d'}, e:'f'}, 'g'=>1.0, 'h'=>nil}.deep_symbolize_keys
# => {:a=>1, :b=>{:c=>{:d=>"d"}, :e=>"f"}, :g=>1.0, :h=>nil}
Vous pouvez également l'étendre facilement pour prendre en charge Arrays
:
class Array
def deep_symbolize_keys
map(&:deep_symbolize_keys)
end
end
{'a'=>1, 'b'=>[{'c'=>{'d'=>'d'}}, {e:'f'}]}.deep_symbolize_keys
# => {:a=>1, :b=>[{:c=>{:d=>"d"}}, {:e=>"f"}]}
Puis-je suggérer:
JSON.parse(hash_value.to_json)
Vous pouvez utiliser:
Il s'agit d'une solution simple à votre problème, mais vous ne devriez envisager de l'utiliser que si vous pouvez être sûr que eval
ne va pas produire quelque chose de désagréable. Si vous contrôlez le contenu du hachage en cours de conversion, cela ne devrait pas poser de problème.
Cette approche pourrait être utilisée pour d'autres types d'objets imbriqués, tels que ceux contenant à la fois des tableaux et des hachages.
Code
def symbolize_hash(h)
eval(h.to_s.gsub(/\"(\w+)\"(?==>)/, ':\1'))
end
Exemples
symbolize_hash(hash4)
#=> {:one=>{:one=> {:one=> {:one=>1, :two=>2, :three=>3},
# :two=> {:one=>1, :two=>2, :three=>3},
# :three=>{:one=>1, :two=>2, :three=>3}},
# :two=> {:one=> {:one=>1, :two=>2, :three=>3},
# :two=> {:one=>1, :two=>2, :three=>3},
# :three=>{:one=>1, :two=>2, :three=>3}},
# :three=>{:one=> {:one=>1, :two=>2, :three=>3},
# :two=> {:one=>1, :two=>2, :three=>3},
# :three=>{:one=>1, :two=>2, :three=>3}}},
# :two=>{:one=> {:one=> {:one=>1, :two=>2, :three=>3},
# ...
# :three=>{:one=>{:one=> {:one=>1, :two=>2, :three=>3},
# ...
# :three=>{:one=>1, :two=>2, :three=>3}}}}
symbolize_hash({'a'=>1, 'b'=>[{'c'=>{'d'=>'d'}}, {e:'f'}]})
#=> {:a=>1, :b=>[{:c=>{:d=>"d"}}, {:e=>"f"}]}
Explication
(?==>)
Dans l'expression rationnelle est une anticipation postive de largeur nulle. ?=
Signifie une anticipation positive; =>
Est la chaîne qui doit suivre immédiatement la correspondance avec \"(\w+)\"
. \1
Dans ':\1'
(Ou j'aurais pu écrire ":\\1"
) Est une chaîne commençant par deux points suivie d'une référence arrière au contenu du groupe de capture # 1, la clé correspondant à \w+
(Sans les guillemets).