Der beste Weg, um Zeichenfolgen in Symbole in Hash umzuwandeln

250

Was ist die (schnellste / sauberste / unkomplizierteste) Möglichkeit, alle Schlüssel in einem Hash von Zeichenfolgen in Symbole in Ruby zu konvertieren?

Dies wäre praktisch, wenn Sie YAML analysieren.

my_hash = YAML.load_file('yml')

Ich möchte verwenden können:

my_hash[:key] 

Eher, als:

my_hash['key']
Bryan M.
quelle
dup ?
Ian Vaughan
80
hash.symbolize_keysund hash.deep_symbolize_keyserledigen Sie den Job, wenn Sie Rails verwenden.
Zaz
Josh, wenn du deinen Kommentar in eine Antwort gesetzt hättest, hätte ich dich gewählt. erfordern 'Schienen'; hash.deep_symbolize_keys funktioniert ziemlich gut in irb oder pry. : D
Douglas G. Allen

Antworten:

239

In Ruby> = 2.5 ( docs ) können Sie Folgendes verwenden:

my_hash.transform_keys(&:to_sym)

Verwenden Sie eine ältere Ruby-Version? Hier ist ein Einzeiler, der den Hash mit den symbolisierten Schlüsseln in einen neuen kopiert:

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

Mit Rails können Sie verwenden:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 
Sarah Mei
quelle
5
Ah, tut mir leid, dass ich unklar bin - injizieren ändert den Anrufer nicht. Sie müssen my_hash = my_hash.inject ({}) {| memo, (k, v) | ausführen memo [k.to_sym] = v; memo}
Sarah Mei
4
Genau das habe ich gesucht. Ich habe es ein wenig modifiziert und einige Zeilen hinzugefügt, um sogar Symbole in eingebetteten Hashes zu erstellen. Werfen
Matt
37
In Ruby 1.9 können Sie each_with_objectwie my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
folgt
8
Dies behandelt keine rekursiven Hashes ... Suchen Sie nach einem einmaligen, aber nicht nach DRY.
Baash05
8
@BryanM. Ich bin sehr spät in diese Diskussion gekommen :-), aber Sie können die .tapMethode auch verwenden , um die Notwendigkeit zu beseitigen, memoam Ende zu bestehen. Ich habe eine bereinigte Version aller Lösungen (auch rekursive) erstellt. Gist.github.com/Integralist/9503099
Integralist
307

Hier ist eine bessere Methode, wenn Sie Rails verwenden:

params. symbolize_keys

Das Ende.

Wenn Sie nicht sind, rippen Sie einfach ihren Code ab (es ist auch im Link):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
Sai
quelle
6
to_options ist ein Alias ​​für sybolize_keys .
ma11hew28
45
Symbolisiert keine verschachtelten Hashes.
Oma
3
Ich habe den Link auf symbolize_keysmit der neuen & funktionierenden (Rails 3) URL umgestellt . Ich habe ursprünglich nur die URL für festgelegt to_options, aber unter diesem Link befindet sich keine Dokumentation. symbolize_keyshat tatsächlich eine Beschreibung, also habe ich diese stattdessen verwendet.
Craig Walker
23
deep_symbolize_keys! . Arbeitet auf Schienen 2+
mastaBlasta
14
Für diejenigen, die neugierig sind, wie man das Gegenteil macht, hash.stringify_keysfunktioniert.
Nick
112

Für den speziellen Fall von YAML in Ruby, wenn die Schlüssel mit 'beginnen: ' , werden sie automatisch als Symbole interniert.

erfordern 'yaml'
erfordern 'pp'
yaml_str = "
Verbindungen:
  - host: host1.example.com
    Port: 10000
  - Host: host2.example.com
    Port: 20000
""
yaml_sym = "
: Verbindungen:
  -: host: host1.example.com
    : port: 10000
  -: host: host2.example.com
    : port: 20000
""
pp yaml_str = YAML.load (yaml_str)
setzt yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
setzt yaml_sym.keys.first.class

Ausgabe:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{"Verbindungen" =>
  [{"port" => 10000, "host" => "host1.example.com"},
   {"port" => 20000, "host" => "host2.example.com"}]}
String
{: Verbindungen =>
  [{: port => 10000 ,: host => "host1.example.com"},
   {: port => 20000 ,: host => "host2.example.com"}]}
Symbol
jrgm
quelle
15
Süss! Gibt es eine Möglichkeit, YAML#load_filealle Schlüssel auf Symbole als Standard festzulegen, anstatt Zeichenfolgen, ohne dass jede Taste mit einem Doppelpunkt beginnen muss?
ma11hew28
63

Noch knapper:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Michael Barton
quelle
6
Dies scheint die naheliegende Wahl zu sein.
Casey Watson
12
Es symbolisiert keine verschachtelten Hashes.
Rgtk
15
Eine besser lesbare Version:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis
60

Wenn Sie Rails verwenden, ist dies viel einfacher. Sie können einen HashWithIndifferentAccess verwenden und auf die Schlüssel sowohl als String als auch als Symbole zugreifen:

my_hash.with_indifferent_access 

siehe auch:

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


Oder Sie können den fantastischen Edelstein "Facets of Ruby" verwenden, der viele Erweiterungen für Ruby Core- und Standard Library-Klassen enthält.

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

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

Tilo
quelle
2
Eigentlich macht das das Gegenteil. Es wird vom Symbol in eine Zeichenfolge konvertiert. Um in ein Symbol zu konvertieren, verwenden Sie my_hash.symbolize_keys
Espen
#symbolize_keys funktioniert nur in Rails - nicht in Ruby / irb. Beachten Sie auch, dass #symbolize_keys bei tief verschachtelten Hashes nicht funktioniert.
Tilo
54

Da können Ruby 2.5.0Sie Hash#transform_keysoder verwenden Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
Sagar Pandya
quelle
Funktioniert auch mit transform_values apidock.com/rails/Hash/transform_values . Das ist wirklich schön, denn es scheint kein Äquivalent zum Ändern von Werten zu geben, wie es es mit stringify_keysoder gibt symbolize_keys.
Mike Vallano
Gibt es eine Möglichkeit, Schlüssel tief zu symbolisieren
Abhay Kumar
Dies sollte die gewählte Lösung nach 2018 sein.
Ivan Wang
26

Hier ist eine Möglichkeit, ein Objekt tief zu symbolisieren

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
Großhandel
quelle
Schön, ich werde mit diesem gehen, auch wenn ich es deep_symbolize umbenennen würde :)
PierrOz
20

Ich mag das Mash- Juwel wirklich .

Sie können tun mash['key'], oder mash[:key], odermash.key

ykaganovich
quelle
2
Das ist ein sehr cooles Juwel! Macht das Arbeiten mit Hashes sehr bequem. Danke dafür!
Asaaki
So schön einfach. Die Neuentwicklung dieses Projekts wird in Hashie ( github.com/intridea/hashie ) fortgesetzt, funktioniert aber immer noch ziemlich ähnlich: github.com/intridea/hashie#keyconversion
jpalmieri
19

Wenn Sie json verwenden und es als Hash verwenden möchten, können Sie dies in Core Ruby tun:

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

symbolize_names : Wenn auf true gesetzt, werden Symbole für die Namen (Schlüssel) in einem JSON-Objekt zurückgegeben. Andernfalls werden Zeichenfolgen zurückgegeben. Zeichenfolgen sind die Standardeinstellung.

Doc: Json # parse symbolize_names

Kennzeichen
quelle
symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)ist eine ziemlich trockene (aber ineffiziente) Möglichkeit, schnell einen Hash mit verschachtelten Schlüsseln als Symbol zu erhalten, wenn er aus einer YAML-Datei stammt.
Cameron Gagnon
12

params.symbolize_keyswird auch funktionieren. Diese Methode wandelt Hash-Schlüssel in Symbole um und gibt einen neuen Hash zurück.

Jae Cho
quelle
26
Diese Methode ist nicht Kern Ruby. Es ist eine Rails-Methode.
Ricardo Acras
10

Eine Änderung an @igorsales Antwort

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
quelle
Es wäre hilfreich, wenn Sie angeben würden, warum Sie das Objekt für Personen geändert haben, die Antworten durchsuchen.
Dbz
9

In Rails können Sie verwenden:

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

Konvertiert zu:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
JayJay
quelle
3
deep_symbolize_keyswird in der Hash- Erweiterung von Rails hinzugefügt , ist jedoch nicht Teil des Ruby-Kerns.
Ryan Crispin Heneise
7

Dies ist mein Einzeiler für verschachtelte Hashes

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
quelle
Zu Ihrer Information, funktioniert nur für Rails. In diesem Fall ist HashWithIndifferentAccess möglicherweise eine bessere Alternative.
Adam Grant
6

Falls der Grund , warum Sie dies tun müssen, darin besteht, dass Ihre Daten ursprünglich von JSON stammen, können Sie jede dieser Analysen überspringen, indem Sie einfach die übergeben:symbolize_names Option auf die Einnahme von JSON.

Keine Rails erforderlich und funktioniert mit Ruby> 1.9

JSON.parse(my_json, :symbolize_names => true)
Adam Grant
quelle
4

Sie könnten faul sein und es einwickeln in lambda:

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

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

Dies würde jedoch nur zum Lesen aus dem Hash funktionieren - nicht zum Schreiben.

Um das zu tun, könnten Sie verwenden Hash#merge

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

Der Init-Block konvertiert die Schlüssel bei Bedarf einmal. Wenn Sie jedoch den Wert für die Zeichenfolgenversion des Schlüssels nach dem Zugriff auf die Symbolversion aktualisieren, wird die Symbolversion nicht aktualisiert.

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}

Sie könnten auch den Init-Block veranlassen, den Hash nicht zu aktualisieren, was Sie vor dieser Art von Fehler schützen würde, aber Sie wären immer noch anfällig für das Gegenteil - das Aktualisieren der Symbolversion würde die String-Version nicht aktualisieren:

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}

Bei diesen ist es also wichtig, zwischen den beiden Schlüsselformen zu wechseln. Bleib bei einem.

Rampion
quelle
3

Würde so etwas wie folgendes funktionieren?

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

Es wird den Hash kopieren, aber das wird Sie die meiste Zeit nicht interessieren. Es gibt wahrscheinlich eine Möglichkeit, dies zu tun, ohne alle Daten zu kopieren.

ChrisInEdmonton
quelle
3

ein kürzerer Einzeiler fwiw:

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

Wie wäre es damit:

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

# my_hash['key'] => "val"
# my_hash[:key]  => "val"
Fredrik Boström
quelle
2

Dies ist für Personen gedacht mruby, die eine symbolize_keysMethode verwenden und nicht definiert haben:

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

Die Methode:

  • symbolisiert nur Schlüssel, die sind String
  • Wenn eine Zeichenfolge symbolisiert, bedeutet dies, dass einige Informationen verloren gehen (ein Teil des Hashs wird überschrieben). Erhöhen Sie a RuntimeError
  • symbolisieren auch rekursiv enthaltene Hashes
  • Geben Sie den symbolisierten Hash zurück
  • funktioniert an Ort und Stelle!
Matteo Ragni
quelle
1
Es gibt einen Tippfehler in Ihrer Methode, Sie haben das !in vergessen symbolize_keys. Ansonsten funktioniert gut.
Ka Mok
1

Das Array, das wir ändern möchten.

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

Machen Sie eine neue Variable als leeres Array, damit wir die Symbole in ".push" können.

Symbole = []

Hier definieren wir eine Methode mit einem Block.

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

Ende des Codes.

Dies ist wahrscheinlich die einfachste Möglichkeit, Zeichenfolgen in Symbole in Ihren Arrays in Ruby zu konvertieren. Erstellen Sie ein Array von Zeichenfolgen, erstellen Sie dann eine neue Variable und setzen Sie die Variable auf ein leeres Array. Wählen Sie dann jedes Element im ersten Array aus, das Sie mit der Methode ".each" erstellt haben. Verwenden Sie dann einen Blockcode, um alle Elemente in Ihrem neuen Array zu "pushen", und konvertieren Sie alle Elemente mit ".intern oder .to_sym" in Symbole.

Symbole sind schneller, weil sie mehr Speicherplatz in Ihrem Code sparen und Sie sie nur einmal verwenden können. Symbole werden am häufigsten für Schlüssel in Hash verwendet, was großartig ist. Ich bin nicht der beste Ruby-Programmierer, aber diese Art von Code hat mir sehr geholfen. Wenn jemand einen besseren Weg kennt, teilen Sie ihn bitte mit, und Sie können diese Methode auch für Hash verwenden!

rubyguest123
quelle
2
Die Frage betraf Hashes, keine Arrays.
Richard_G
1

Wenn Sie Vanille-Rubin-Lösung möchten und da ich keinen Zugang zu ActiveSupporthier habe, ist tief symbolisieren Lösung (sehr ähnlich zu vorherigen)

    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
quelle
1

Ab Psych 3.0 können Sie die Option symbolize_names: hinzufügen

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

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

Hinweis: Wenn Sie eine niedrigere Psych-Version als 3.0 haben, symbolize_names:wird diese stillschweigend ignoriert.

Mein Ubuntu 18.04 enthält es sofort mit Ruby 2.5.1p57

Rodrigo Estebanez
quelle
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
quelle
Sie können ain Klammern setzen, um das Blockargument zu zerlegen und es noch knapper zu machen. Siehe meine Antwort zum Beispiel.
Michael Barton
0

Dies ist nicht gerade ein Einzeiler, aber es verwandelt alle Zeichenfolgenschlüssel in Symbole, auch die verschachtelten:

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
quelle
0

Ich mag diesen Einzeiler, wenn ich Rails nicht verwende, weil ich dann keinen zweiten Hash erstellen und zwei Datensätze speichern muss, während ich ihn verarbeite:

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 gibt den Wert des gelöschten Schlüssels zurück

nevets138
quelle
0

Facets 'Hash # deep_rekey ist auch eine gute Option, insbesondere:

  • Wenn Sie in Ihrem Projekt Verwendung für anderen Zucker aus Facetten finden,
  • Wenn Sie die Lesbarkeit von Code gegenüber kryptischen Einzeilern bevorzugen.

Stichprobe:

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

In Ruby finde ich, dass dies die einfachste und verständlichste Methode ist, um String-Schlüssel in Hashes in Symbole umzuwandeln:

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

Für jeden Schlüssel im Hash rufen wir delete auf, wodurch er aus dem Hash entfernt wird (auch delete gibt den Wert zurück, der dem gelöschten Schlüssel zugeordnet ist ), und wir setzen diesen sofort gleich dem symbolisierten Schlüssel.


quelle
0

Ähnlich wie bei früheren Lösungen, jedoch etwas anders geschrieben.

  • Dies ermöglicht einen Hash, der verschachtelt ist und / oder Arrays enthält.
  • Holen Sie sich die Konvertierung von Schlüsseln in eine Zeichenfolge als Bonus.
  • Der Code mutiert den übergebenen Hash nicht.

    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
quelle