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']
Bryan M.
la source
dup ?
Ian Vaughan
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 
Sarah Mei
la source
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
Sai
la source
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
jrgm
la source
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]}]
Michael Barton
la source
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

Tilo
la source
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}
Sagar Pandya
la source
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
igorsales
la source
gentil, j'irai avec celui-ci même si je le renommerais deep_symbolize :)
PierrOz
20

J'aime vraiment le bijou Mash .

vous pouvez faire mash['key'], ou mash[:key], oumash.key

ykaganovich
la source
2
C'est un bijou très cool! Rend le travail avec les hachages très confortable. Merci pour cela!
asaaki
Si magnifiquement simple. Le nouveau développement de ce projet se poursuit dans Hashie ( github.com/intridea/hashie ) mais cela fonctionne à peu près de la même manière: github.com/intridea/hashie#keyconversion
jpalmieri
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

marque
la source
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.

Jae Cho
la source
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
Tony
la source
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"}}}
Jay Jay
la source
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
Nick Dobson
la source
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)
Adam Grant
la source
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.

rampion
la source
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.

ChrisInEdmonton
la source
3

un fwiw à une ligne plus court:

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

Que dis-tu de ça:

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

# my_hash['key'] => "val"
# my_hash[:key]  => "val"
Fredrik Boström
la source
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!
Matteo Ragni
la source
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!

rubyguest123
la source
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
Haris Krajina
la source
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

Rodrigo Estebanez
la source
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}
Vlad Khomich
la source
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
ChristofferJoergensen
la source
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

nevets138
la source
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
Sergikon
la source
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.


la source
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
jham
la source