Il modo migliore per convertire stringhe in simboli in hash


250

Qual è il modo (più veloce / più pulito / più semplice) per convertire tutte le chiavi in ​​un hash da stringhe a simboli in Ruby?

Ciò sarebbe utile quando si analizza YAML.

my_hash = YAML.load_file('yml')

Mi piacerebbe poter usare:

my_hash[:key] 

Piuttosto che:

my_hash['key']


80
hash.symbolize_keyse hash.deep_symbolize_keysfai il lavoro se stai usando Rails.
Zaz,

Josh se avessi inserito il tuo commento in una risposta, ti avrei votato. richiedono "rotaie"; hash.deep_symbolize_keys funziona abbastanza bene in irb o in leva. : D
Douglas G. Allen

Risposte:


239

In Ruby> = 2.5 ( documenti ) puoi usare:

my_hash.transform_keys(&:to_sym)

Usi la versione precedente di Ruby? Ecco un one-liner che copia l'hash in uno nuovo con i tasti simboleggiati:

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

Con Rails puoi usare:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 

5
Ah, scusa se non sono chiaro: l'iniezione non modifica il chiamante. Devi fare my_hash = my_hash.inject ({}) {| memo, (k, v) | memo [k.to_sym] = v; memo}
Sarah Mei,

4
Questo è esattamente quello che stavo cercando. L'ho modificato un po 'e ho aggiunto alcune righe per persino creare simboli negli hash accoppiati. Dai
Matt

37
In Ruby 1.9 puoi usare each_with_objectcosì:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd,

8
questo non gestisce gli hash ricorsivi ... Trova per una tantum ma non per DRY.
baash05,

8
@BryanM. Sono entrato in questa discussione molto tardi :-) ma puoi anche usare il .tapmetodo per rimuovere la necessità di passare memoalla fine. Ho creato una versione ripulita di tutte le soluzioni (anche ricorsive) gist.github.com/Integralist/9503099
Integralist

307

Ecco un metodo migliore, se stai usando Rails:

Parametri. symbolize_keys

La fine.

In caso contrario, basta strappare il loro codice (è anche nel collegamento):

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

6
to_options è un alias per sybolize_keys .
ma11hew28,

45
Non simboleggia gli hash nidificati.
Oma

3
Ho cambiato il collegamento symbolize_keyscon l'URL nuovo e funzionante (Rails 3). Inizialmente avevo appena corretto l'URL per to_options, ma non c'è documentazione a questo link. symbolize_keysin realtà ha una descrizione, quindi l'ho usato invece.
Craig Walker,

23
deep_symbolize_keys! . Funziona su binari 2+
mastaBlasta il

14
Per quelli curiosi come fare il contrario, hash.stringify_keysfunziona.
Nick,

112

Per il caso specifico di YAML in Ruby, se le chiavi iniziano con ": ", verranno automaticamente internate come simboli.

richiedi 'yaml'
richiede 'pp'
yaml_str = "
connessioni:
  - host: host1.example.com
    porto: 10000
  - host: host2.example.com
    porto: 20000
"
yaml_sym = "
: collegamenti:
  -: host: host1.example.com
    : porta: 10000
  -: host: host2.example.com
    : porto: 20000
"
pp yaml_str = YAML.load (yaml_str)
mette yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
mette yaml_sym.keys.first.class

Produzione:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{ "connessioni" =>
  [{"port" => 10000, "host" => "host1.example.com"},
   {"port" => 20000, "host" => "host2.example.com"}]}
Corda
{: Collegamenti =>
  [{: port => 10000,: host => "host1.example.com"},
   {: port => 20000,: host => "host2.example.com"}]}
Simbolo

15
Dolce! C'è un modo per impostare YAML#load_filetutti i tasti sui valori predefiniti invece che sulle stringhe senza dover iniziare ogni tasto con due punti?
ma11hew28,

63

Ancora più conciso:

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

6
Questa sembra la scelta ovvia.
Casey Watson,

12
Non simboleggia gli hash nidificati.
rgtk,

15
Una versione più leggibile:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis,

60

se stai usando Rails, è molto più semplice: puoi usare un HashWithIndifferentAccess e accedere alle chiavi sia come String che come Simboli:

my_hash.with_indifferent_access 

Guarda anche:

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


Oppure puoi usare la fantastica gemma "Facets of Ruby", che contiene molte estensioni alle classi Ruby Core e Standard Library.

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

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


2
In realtà fa il contrario. Converte da simbolo a stringa. Per convertire in un simbolo utilizzare my_hash.symbolize_keys
Espen

#symbolize_keys funziona solo in Rails, non in Ruby / Irb. Inoltre, #symbolize_keys non funziona su hash profondamente nidificati.
Tilo,

54

Dal momento che Ruby 2.5.0è possibile utilizzare Hash#transform_keyso Hash#transform_keys!.

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

Funziona anche con transform_values apidock.com/rails/Hash/transform_values . È davvero bello perché non sembra esserci un equivalente per modificare i valori come c'è con stringify_keyso symbolize_keys.
Mike Vallano,

c'è un modo per simboleggiare profondamente le chiavi
Abhay Kumar,

Questa dovrebbe essere la soluzione scelta dopo il 2018.
Ivan Wang


26

Ecco un modo per simboleggiare in profondità un oggetto

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

bello, andrò con questo anche se lo rinominassi deep_symbolize :)
PierrOz,


19

Se stai usando json e vuoi usarlo come hash, nel core Ruby puoi farlo:

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

symbolize_names : se impostato su true, restituisce i simboli per i nomi (chiavi) in un oggetto JSON. Altrimenti vengono restituite le stringhe. Le stringhe sono quelle predefinite.

Doc: Json # parse symbolize_names


symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)è un modo piuttosto ASCIUTTO (ma inefficiente) per ottenere rapidamente un hash con chiavi nidificate come simboli se proviene da un file YAML.
Cameron Gagnon il

12

params.symbolize_keysfunzionerà anche. Questo metodo trasforma le chiavi hash in simboli e restituisce un nuovo hash.


26
Questo metodo non è il nucleo di Ruby. È un metodo Rails.
Ricardo Acras,

10

Una modifica alla risposta di @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

Sarebbe utile se includessi il motivo per cui stai modificando l'oggetto per le persone che scansionano le risposte.
Dbz,

9

In Rails puoi usare:

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

Converte in:

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

3
deep_symbolize_keysviene aggiunto nell'estensione Hash di Rails , ma non fa parte del core di Ruby.
Ryan Crispin Heneise,


7

Questa è la mia unica fodera per gli hash nidificati

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

Cordiali saluti, funziona solo per Rails. In tal caso, HashWithIndifferentAccess potrebbe essere un'alternativa migliore.
Adam Grant,

6

Nel caso in cui sia necessario eseguire questa operazione perché i dati sono originari di JSON, è possibile saltare qualsiasi analisi analizzando semplicemente l' :symbolize_namesopzione al momento dell'ingestione di JSON.

Nessun binario richiesto e funziona con Ruby> 1.9

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

4

Potresti essere pigro e avvolgerlo in un lambda:

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

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

Ma questo funzionerebbe solo per la lettura dell'hash, non per la scrittura.

Per farlo, potresti usare Hash#merge

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

Il blocco init convertirà le chiavi una volta su richiesta, sebbene se si aggiorna il valore per la versione stringa della chiave dopo aver effettuato l'accesso alla versione simbolo, la versione simbolo non verrà aggiornata.

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}

Potresti anche avere il blocco init che non aggiorna l'hash, il che ti proteggerebbe da quel tipo di errore, ma saresti comunque vulnerabile al contrario - l'aggiornamento della versione del simbolo non aggiornerebbe la versione della stringa:

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}

Quindi la cosa da fare attenzione con questi è il passaggio tra le due forme chiave. Stick con uno.


3

Qualcosa come il seguente lavoro?

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

Copierà l'hash, ma non te ne occuperai per la maggior parte del tempo. Probabilmente c'è un modo per farlo senza copiare tutti i dati.


3

una prima fila più corta:

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

2

Cosa ne pensi di questo:

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

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

2

Questo è per le persone che usano mrubye non hanno alcun symbolize_keysmetodo definito:

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

Il metodo:

  • simboleggia solo le chiavi che lo sono String
  • se simbolizzare una stringa significa perdere alcune informazioni (sovrascrivere parte dell'hash) sollevare a RuntimeError
  • simboleggiano anche hash ricorsivamente contenuti
  • restituisce l'hash simbolizzato
  • funziona sul posto!

1
C'è un errore di battitura nel tuo metodo, hai dimenticato l' !in symbolize_keys. Altrimenti funziona bene.
Ka Mok,

1

La matrice che vogliamo cambiare.

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

Crea una nuova variabile come un array vuoto in modo che possiamo ".push" i simboli in.

simboli = []

Ecco dove definiamo un metodo con un blocco.

strings.each {| x | symbols.push (x.intern)}

Fine del codice

Quindi questo è probabilmente il modo più semplice per convertire stringhe in simboli nei tuoi array in Ruby. Crea una matrice di stringhe, quindi crea una nuova variabile e imposta la variabile su una matrice vuota. Quindi selezionare ciascun elemento nel primo array creato con il metodo ".each". Quindi utilizzare un codice di blocco per ".push" tutti gli elementi nel nuovo array e utilizzare ".intern o .to_sym" per convertire tutti gli elementi in simboli.

I simboli sono più veloci perché risparmiano più memoria nel tuo codice e puoi usarli solo una volta. I simboli sono più comunemente usati per le chiavi nell'hash, il che è fantastico. Non sono il miglior programmatore di ruby, ma questa forma di codice mi ha aiutato molto. Se qualcuno conosce un modo migliore per favore condividi e puoi usare questo metodo anche per l'hash!


2
La domanda riguardava gli hash, non gli array.
Richard_G,

1

Se desideri una soluzione di rubino alla vaniglia e poiché non ho accesso a ActiveSupportquesto è una soluzione simbolica profonda (molto simile alle precedenti)

    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

A partire da Psych 3.0 è possibile aggiungere l' opzione symbolize_names:

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

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

Nota: se si dispone di una versione di Psych inferiore a 3.0 symbolize_names:verrà automaticamente ignorato.

Ubuntu 18.04 lo include immediatamente con 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}

Puoi racchiudere le aparentesi quadre per scomporre l'argomento block per renderlo ancora più conciso. Vedi la mia risposta per esempio.
Michael Barton,

0

Questo non è esattamente un one-liner, ma trasforma tutte le chiavi di stringa in simboli, anche quelli nidificati:

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

Mi piace questo one-liner, quando non utilizzo Rails, perché non devo creare un secondo hash e conservare due set di dati mentre lo sto elaborando:

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 restituisce il valore della chiave eliminata


0

L'Hash di facce # deep_rekey è anche una buona opzione, in particolare:

  • se nel tuo progetto trovi uso di altro zucchero proveniente dalle sfaccettature,
  • se preferisci la leggibilità del codice rispetto a una riga di testo crittografica.

Campione:

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

0

In ruby ​​trovo che questo sia il modo più semplice e facile da capire per trasformare le chiavi di stringa in hash in simboli:

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

Per ogni chiave nell'hash chiamiamo delete su di essa che lo rimuove dall'hash (anche delete restituisce il valore associato alla chiave che è stata eliminata) e impostiamo immediatamente questo uguale alla chiave simbolizzata.


0

Simile alle soluzioni precedenti ma scritto in modo leggermente diverso.

  • Ciò consente un hash nidificato e / o con array.
  • Ottieni la conversione di chiavi in ​​una stringa come bonus.
  • Il codice non muta l'hash passato.

    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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.