web-dev-qa-db-fra.com

Comment symboliser élégamment les touches pour un hachage «imbriqué»

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

46
SHS

Il y a quelques façons de le faire

  1. Il y a une méthode deep_symbolize_keys Dans Rails

    hash.deep_symbolize_keys!

  2. 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.

  3. 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

  4. 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.

95
jvnill

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
    
25
Mikhail Chuprynski

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
6
Alexander

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"}]}
3
Uri Agassi

Puis-je suggérer:

JSON.parse(hash_value.to_json)
2
mirage

Vous pouvez utiliser:

  • Hash # to_s pour convertir le hachage en chaîne;
  • String # gsub avec une expression régulière pour convertir les clés des chaînes en représentations de symboles; et alors
  • Kernel # eval pour reconvertir la chaîne en hachage.

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).

1
Cary Swoveland