Meilleure façon de convertir des chaînes en symboles en hachage


250

Quel est le moyen (le plus rapide / le plus propre / le plus simple) de convertir toutes les clés d'un hachage de chaînes en symboles en Ruby?

Ce serait pratique lors de l'analyse de YAML.

my_hash = YAML.load_file('yml')

J'aimerais pouvoir utiliser:

my_hash[:key] 

Plutôt que:

my_hash['key']


80
hash.symbolize_keyset hash.deep_symbolize_keysfaites le travail si vous utilisez Rails.
Zaz

Josh, si vous aviez mis votre commentaire dans une réponse, je vous aurais voté. nécessitent des 'rails'; hash.deep_symbolize_keys fonctionne plutôt bien en irb ou en pry. : D
Douglas G. Allen

Réponses:


239

Dans Ruby> = 2.5 ( docs ), vous pouvez utiliser:

my_hash.transform_keys(&:to_sym)

Vous utilisez une ancienne version de Ruby? Voici un one-liner qui copiera le hachage dans un nouveau avec les clés symbolisées:

my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}

Avec Rails, vous pouvez utiliser:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 

5
Ah, désolé de ne pas être clair - l'injection ne modifie pas l'appelant. Vous devez faire my_hash = my_hash.inject ({}) {| memo, (k, v) | mémo [k.to_sym] = v; mémo}
Sarah Mei

4
C'est exactement ce que je cherchais. Je l'ai un peu modifié et ajouté quelques lignes pour même créer des symboles dans des hachages imbriqués. Jetez un oeil ici, si vous êtes intéressé: any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
Matt

37
Dans Ruby 1.9, vous pouvez utiliser each_with_objectcomme ceci:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd

8
cela ne gère pas les hachages récursifs ... Recherchez un élément unique mais pas pour DRY.
baash05

8
@BryanM. Je suis entré dans cette discussion très tard :-) mais vous pouvez également utiliser la .tapméthode pour supprimer le besoin de passer memoà la fin. J'ai créé une version nettoyée de toutes les solutions (récursives également) gist.github.com/Integralist/9503099
Integralist

307

Voici une meilleure méthode, si vous utilisez Rails:

params. symbolize_keys

La fin.

Si vous ne l'êtes pas, il vous suffit de déchirer leur code (c'est aussi dans le lien):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end

6
to_options est un alias pour sybolize_keys .
ma11hew28

45
Ne symbolise pas les hachages imbriqués.
oma

3
J'ai basculé le lien vers symbolize_keysl'URL nouvelle et fonctionnelle (Rails 3). À l'origine, je viens de corriger l'URL de to_options, mais il n'y a aucune documentation sur ce lien. symbolize_keysa en fait une description, donc je l'ai utilisé à la place.
Craig Walker

23
deep_symbolize_keys! . Fonctionne sur rails 2+
mastaBlasta

14
Pour ceux curieux de savoir comment faire l'inverse, ça marche hash.stringify_keys.
Nick

112

Pour le cas spécifique de YAML en Ruby, si les clés commencent par ' :', elles seront automatiquement internées en tant que symboles.

nécessite 'yaml'
nécessite 'pp'
yaml_str = "
Connexions:
  - hôte: host1.example.com
    port: 10000
  - hôte: host2.example.com
    port: 20000
"
yaml_sym = "
:Connexions:
  -: hôte: host1.example.com
    : port: 10000
  -: hôte: host2.example.com
    : port: 20000
"
pp yaml_str = YAML.load (yaml_str)
met yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
met yaml_sym.keys.first.class

Production:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{"connections" =>
  [{"port" => 10000, "host" => "host1.example.com"},
   {"port" => 20000, "host" => "host2.example.com"}]}
Chaîne
{: connections =>
  [{: port => 10000,: host => "host1.example.com"},
   {: port => 20000,: host => "host2.example.com"}]}
symbole

15
Doux! Existe-t-il un moyen de définir YAML#load_filepar défaut toutes les clés sur des symboles au lieu de chaînes sans avoir à commencer chaque clé par deux points?
ma11hew28

63

Encore plus concis:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]

6
Cela semble être le choix évident.
Casey Watson

12
Il ne symbolise pas les hachages imbriqués.
rgtk

15
Une version plus lisible:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis

60

si vous utilisez Rails, c'est beaucoup plus simple - vous pouvez utiliser un HashWithIndifferentAccess et accéder aux clés à la fois sous forme de chaîne et de symboles:

my_hash.with_indifferent_access 

voir également:

http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html


Ou vous pouvez utiliser le génial "Facets of Ruby" Gem, qui contient beaucoup d'extensions aux classes Ruby Core et Standard Library.

  require 'facets'
  > {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
    =>  {:some=>"thing", :foo=>"bar}

voir aussi: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash


2
En fait, cela fait le contraire. Il convertit du symbole en une chaîne. Pour convertir en symbole, utilisez my_hash.symbolize_keys
Espen

#symbolize_keys ne fonctionne que dans Rails - pas en plain Ruby / irb. Notez également que #symbolize_keys ne fonctionne pas sur les hachages profondément imbriqués.
Tilo

54

Puisque Ruby 2.5.0vous pouvez utiliser Hash#transform_keysou Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}

Fonctionne également avec transform_values apidock.com/rails/Hash/transform_values . C'est vraiment bien car il ne semble pas y avoir d'équivalent pour modifier les valeurs comme il y a avec stringify_keysou symbolize_keys.
Mike Vallano

existe-t-il un moyen de symboliser profondément les touches
Abhay Kumar

Cela devrait être la solution choisie après 2018.
Ivan Wang


26

Voici un moyen de symboliser profondément un objet

def symbolize(obj)
    return obj.inject({}){|memo,(k,v)| memo[k.to_sym] =  symbolize(v); memo} if obj.is_a? Hash
    return obj.inject([]){|memo,v    | memo           << symbolize(v); memo} if obj.is_a? Array
    return obj
end

gentil, j'irai avec celui-ci même si je le renommerais deep_symbolize :)
PierrOz


19

Si vous utilisez json et que vous souhaitez l'utiliser comme hachage, dans le noyau Ruby, vous pouvez le faire:

json_obj = JSON.parse(json_str, symbolize_names: true)

symbolize_names : si la valeur est true, renvoie les symboles des noms (clés) dans un objet JSON. Sinon, les chaînes sont retournées. Les chaînes sont la valeur par défaut.

Doc: Json # parse symbolize_names


symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)est un moyen assez SEC (mais inefficace) d'obtenir rapidement un hachage avec des clés imbriquées comme symboles si elles proviennent d'un fichier YAML.
Cameron Gagnon

12

params.symbolize_keysfonctionnera également. Cette méthode transforme les clés de hachage en symboles et renvoie un nouveau hachage.


26
Cette méthode n'est pas le noyau de Ruby. Il s'agit d'une méthode Rails.
Ricardo Acras

10

Une modification de la réponse @igorsales

class Object
  def deep_symbolize_keys
    return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
    return self.inject([]){|memo,v    | memo           << v.deep_symbolize_keys; memo} if self.is_a? Array
    return self
  end
end

Il serait utile d'indiquer pourquoi vous modifiez l'objet pour les personnes parcourant les réponses.
Dbz

9

Dans Rails, vous pouvez utiliser:

{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!

Convertit en:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}

3
deep_symbolize_keysest ajouté dans l' extension Hash de Rails , mais il ne fait pas partie du noyau Ruby.
Ryan Crispin Heneise


7

C'est ma seule doublure pour les hachages imbriqués

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end

Pour info, ne fonctionne que pour Rails. Dans ce cas, HashWithIndifferentAccess peut être une meilleure alternative.
Adam Grant

6

Dans le cas où la raison pour laquelle vous devez le faire est que vos données proviennent à l'origine de JSON, vous pouvez ignorer l'une de ces analyses en passant simplement l' :symbolize_namesoption lors de l'ingestion de JSON.

Aucun Rails requis et fonctionne avec Ruby> 1.9

JSON.parse(my_json, :symbolize_names => true)

4

Vous pourriez être paresseux et l'envelopper dans un lambda:

my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }

my_lamb[:a] == my_hash['a'] #=> true

Mais cela ne fonctionnerait que pour lire à partir du hachage - pas pour écrire.

Pour ce faire, vous pouvez utiliser Hash#merge

my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))

Le bloc init convertira les clés une fois sur demande, mais si vous mettez à jour la valeur de la version chaîne de la clé après avoir accédé à la version du symbole, la version du symbole ne sera pas mise à jour.

irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a]  # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a]  # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}

Vous pouvez également demander au bloc init de ne pas mettre à jour le hachage, ce qui vous protégerait de ce type d'erreur, mais vous seriez toujours vulnérable à l'inverse - la mise à jour de la version du symbole ne mettrait pas à jour la version de la chaîne:

irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}

Donc, la chose à laquelle il faut faire attention est de basculer entre les deux formes clés. Restez avec un.


3

Souhaitez-vous quelque chose comme le travail suivant?

new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }

Cela copiera le hachage, mais vous ne vous en soucierez pas la plupart du temps. Il existe probablement un moyen de le faire sans copier toutes les données.


3

un fwiw à une ligne plus court:

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }

2

Que dis-tu de ça:

my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))

# my_hash['key'] => "val"
# my_hash[:key]  => "val"

2

Ceci est destiné aux personnes qui utilisent mrubyet n'ont symbolize_keysdéfini aucune méthode:

class Hash
  def symbolize_keys!
    self.keys.each do |k|
      if self[k].is_a? Hash
        self[k].symbolize_keys!
      end
      if k.is_a? String
        raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
        self[k.to_sym] = self[k]
        self.delete(k)
      end
    end
    return self
  end
end

La méthode:

  • symbolise uniquement les clés qui sont String
  • si symboliser une chaîne signifie perdre certaines informations (écraser une partie du hachage), élever un RuntimeError
  • symbolise également les hachages récursivement contenus
  • retourner le hachage symbolisé
  • fonctionne en place!

1
Il y a une faute de frappe dans votre méthode, vous avez oublié le !dans symbolize_keys. Sinon, ça marche bien.
Ka Mok

1

Le tableau que nous voulons changer.

strings = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]

Créez une nouvelle variable sous forme de tableau vide afin que nous puissions ".push" les symboles dans.

symboles = []

Voici où nous définissons une méthode avec un bloc.

strings.each {| x | symboles.pousser (x.intern)}

Fin du code.

C'est donc probablement le moyen le plus simple de convertir des chaînes en symboles dans vos tableaux dans Ruby. Créez un tableau de chaînes, puis créez une nouvelle variable et définissez la variable sur un tableau vide. Sélectionnez ensuite chaque élément du premier tableau que vous avez créé avec la méthode ".each". Utilisez ensuite un code de bloc pour ".push" tous les éléments de votre nouveau tableau et utilisez ".intern ou .to_sym" pour convertir tous les éléments en symboles.

Les symboles sont plus rapides car ils économisent plus de mémoire dans votre code et vous ne pouvez les utiliser qu'une seule fois. Les symboles sont le plus souvent utilisés pour les clés de hachage, ce qui est génial. Je ne suis pas le meilleur programmeur ruby, mais cette forme de code m'a beaucoup aidé.Si quelqu'un connaît une meilleure façon de partager, vous pouvez également utiliser cette méthode pour le hachage!


2
La question concernait les hachages, pas les tableaux.
Richard_G

1

Si vous souhaitez une solution de rubis vanille et que moi, je n'ai pas accès ActiveSupportici, c'est une solution de symbolisation profonde (très similaire aux précédentes)

    def deep_convert(element)
      return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
      return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
      element
    end

1

À partir de Psych 3.0, vous pouvez ajouter l' option symbolize_names:

Psych.load("---\n foo: bar") # => {"foo"=>"bar"}

Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}

Remarque: si vous avez une version Psych inférieure à 3.0, symbolize_names:elle sera silencieusement ignorée.

Mon Ubuntu 18.04 l'inclut de la boîte avec ruby ​​2.5.1p57


0
ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
 => {"aaa"=>1, "bbb"=>2} 
ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
 => {:aaa=>1, :bbb=>2}

Vous pouvez encapsuler aentre crochets pour décomposer l'argument de bloc afin de rendre cela encore plus concis. Voir ma réponse par exemple.
Michael Barton

0

Ce n'est pas exactement une ligne, mais cela transforme toutes les clés de chaîne en symboles, également les imbriquées:

def recursive_symbolize_keys(my_hash)
  case my_hash
  when Hash
    Hash[
      my_hash.map do |key, value|
        [ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
      end
    ]
  when Enumerable
    my_hash.map { |value| recursive_symbolize_keys(value) }
  else
    my_hash
  end
end

0

J'aime ce one-liner, lorsque je n'utilise pas Rails, car je n'ai pas besoin de faire un deuxième hachage et de conserver deux ensembles de données pendant que je le traite:

my_hash = { "a" => 1, "b" => "string", "c" => true }

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }

my_hash
=> {:a=>1, :b=>"string", :c=>true}

Hash # delete renvoie la valeur de la clé supprimée


0

Hash # deep_rekey de Facets est également une bonne option, en particulier:

  • si vous trouvez l'utilisation d'autres sucres de facettes dans votre projet,
  • si vous préférez la lisibilité du code par rapport à des lignes simples cryptées.

Échantillon:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey

0

En rubis, je trouve que c'est le moyen le plus simple et le plus facile à comprendre pour transformer les clés de chaîne en hachages en symboles:

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}

Pour chaque clé du hachage, nous appelons la suppression, ce qui la supprime du hachage (la suppression renvoie également la valeur associée à la clé qui a été supprimée) et nous la définissons immédiatement égale à la clé symbolisée.


0

Semblable aux solutions précédentes mais écrit un peu différemment.

  • Cela permet un hachage imbriqué et / ou des tableaux.
  • Obtenez la conversion de clés en chaîne en bonus.
  • Le code ne modifie pas le hachage transmis.

    module HashUtils
      def symbolize_keys(hash)
        transformer_function = ->(key) { key.to_sym }
        transform_keys(hash, transformer_function)
      end
    
      def stringify_keys(hash)
        transformer_function = ->(key) { key.to_s }
        transform_keys(hash, transformer_function)
      end
    
      def transform_keys(obj, transformer_function)
        case obj
        when Array
          obj.map{|value| transform_keys(value, transformer_function)}
        when Hash
          obj.each_with_object({}) do |(key, value), hash|
            hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
          end
        else
          obj
        end
      end
    end
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.