Cara terbaik untuk mengubah string menjadi simbol dalam hash

250

Apa cara (tercepat / terbersih / mudah) untuk mengubah semua kunci dalam hash dari string ke simbol di Ruby?

Ini akan berguna saat mengurai YAML.

my_hash = YAML.load_file('yml')

Saya ingin dapat menggunakan:

my_hash[:key] 

Daripada:

my_hash['key']
Bryan M.
sumber
dup ?
Ian Vaughan
80
hash.symbolize_keysdan hash.deep_symbolize_keyslakukan pekerjaan jika Anda menggunakan Rails.
Zaz
Josh jika Anda mau memberikan komentar Anda ke jawaban, saya akan memilih Anda. memerlukan 'rails'; hash.deep_symbolize_keys bekerja cukup baik di irb atau pry. : D
Douglas G. Allen

Jawaban:

239

Di Ruby> = 2.5 ( docs ) Anda dapat menggunakan:

my_hash.transform_keys(&:to_sym)

Menggunakan versi Ruby yang lebih lama? Berikut ini adalah satu-liner yang akan menyalin hash ke yang baru dengan tombol yang dilambangkan:

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

Dengan Rails Anda dapat menggunakan:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 
Sarah Mei
sumber
5
Ah, maaf karena tidak jelas - suntikan tidak mengubah penelepon. Anda perlu melakukan my_hash = my_hash.inject ({}) {| memo, (k, v) | memo [k.to_sym] = v; memo}
Sarah Mei
4
Itulah tepatnya yang saya cari. Saya memodifikasinya sedikit dan menambahkan beberapa baris bahkan untuk membuat simbol di hash bersarang. Silahkan lihat di sini, jika Anda tertarik: any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
Matt
37
Di Ruby 1.9, Anda dapat menggunakan each_with_objectseperti:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd
8
ini tidak menangani hash rekursif ... Temukan sekali saja tetapi tidak untuk KERING.
baash05
8
@BryanM Saya telah datang ke diskusi ini sangat terlambat :-) tetapi Anda juga dapat menggunakan .tapmetode ini untuk menghapus kebutuhan untuk lulus memodi akhir. Saya telah membuat versi yang dibersihkan dari semua solusi (yang rekursif juga) gist.github.com/Integralist/9503099
Integralist
307

Berikut metode yang lebih baik, jika Anda menggunakan Rails:

params. symbolize_keys

Tamat.

Jika tidak, lepas saja kode mereka (itu juga ada di tautan):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
Sai
sumber
6
to_options adalah alias untuk sybolize_keys .
ma11hew28
45
Tidak akan melambangkan hash bersarang.
oma
3
Saya mengganti tautan symbolize_keysdengan URL baru & yang berfungsi (Rails 3). Saya awalnya hanya memperbaiki URL to_options, tetapi tidak ada dokumentasi di tautan itu. symbolize_keyssebenarnya memiliki deskripsi, jadi saya menggunakannya sebagai gantinya.
Craig Walker
23
deep_symbolize_keys! . Bekerja pada rel 2+
mastaBlasta
14
Bagi mereka yang penasaran bagaimana melakukan yang sebaliknya, hash.stringify_keysbekerjalah.
Nick
112

Untuk kasus spesifik YAML di Ruby, jika kunci dimulai dengan ' :', mereka akan secara otomatis diinternir sebagai simbol.

membutuhkan 'yaml'
membutuhkan 'pp'
yaml_str = "
koneksi:
  - host: host1.example.com
    port: 10000
  - host: host2.example.com
    port: 20000
"
yaml_sym = "
: koneksi:
  -: host: host1.example.com
    : port: 10000
  -: host: host2.example.com
    : port: 20000
"
pp yaml_str = YAML.load (yaml_str)
menempatkan yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
menempatkan yaml_sym.keys.first.class

Keluaran:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{"koneksi" =>
  [{"port" => 10000, "host" => "host1.example.com"},
   {"port" => 20000, "host" => "host2.example.com"}]}
Tali
{: koneksi =>
  [{: port => 10000,: host => "host1.example.com"},
   {: port => 20000,: host => "host2.example.com"}]}
Simbol
jrgm
sumber
15
Manis! Apakah ada cara untuk mengatur YAML#load_fileke default semua kunci untuk simbol alih-alih string tanpa harus memulai setiap kunci dengan titik dua?
ma11hew28
63

Bahkan lebih singkat:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Michael Barton
sumber
6
Ini sepertinya pilihan yang jelas.
Casey Watson
12
Itu tidak melambangkan hash bersarang.
rgtk
15
Versi yang lebih mudah dibaca:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis
60

jika Anda menggunakan Rails, ini jauh lebih sederhana - Anda dapat menggunakan HashWithIndifferentAccess dan mengakses kunci baik sebagai String maupun sebagai Simbol:

my_hash.with_indifferent_access 

Lihat juga:

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


Atau Anda dapat menggunakan Permata "Aspek Ruby" yang mengagumkan, yang berisi banyak ekstensi untuk kelas-kelas Ruby Core dan Perpustakaan Standar.

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

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

Tilo
sumber
2
Sebenarnya itu yang terjadi sebaliknya. Itu mengkonversi dari simbol ke string. Untuk mengonversi ke simbol gunakan my_hash.symbolize_keys
Espen
#symbolize_keys hanya berfungsi di Rails - bukan di Ruby / irb biasa. Perhatikan juga bahwa #symbolize_keys tidak berfungsi pada hash yang bersarang secara mendalam.
Tilo
54

Karena Ruby 2.5.0Anda dapat menggunakan Hash#transform_keysatau Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
Sagar Pandya
sumber
Juga berfungsi dengan transform_values apidock.com/rails/Hash/transform_values . Itu sangat bagus karena sepertinya tidak ada padanan untuk memodifikasi nilai seperti ada dengan stringify_keysatau symbolize_keys.
Mike Vallano
apakah ada cara untuk melambangkan kunci
Abhay Kumar
Ini harus menjadi solusi yang dipilih setelah 2018.
Ivan Wang
26

Inilah cara untuk melambangkan objek secara mendalam

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
sumber
bagus, saya akan pergi dengan yang ini bahkan jika saya akan menamainya deep_symbolize :)
PierrOz
20

Saya sangat suka permata Mash .

Anda dapat melakukannya mash['key'], atau mash[:key], ataumash.key

ykaganovich
sumber
2
Itu permata yang sangat keren! Membuat bekerja dengan hash sangat nyaman. Terima kasih untuk ini!
asaaki
Sangat sederhana dan indah. Perkembangan baru pada proyek ini sedang dilanjutkan di Hashie ( github.com/intridea/hashie ) tetapi masih bekerja dengan cara yang hampir sama: github.com/intridea/hashie#keyconversion
jpalmieri
19

Jika Anda menggunakan json, dan ingin menggunakannya sebagai hash, di Ruby inti Anda bisa melakukannya:

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

symbolize_names : Jika disetel ke true, mengembalikan simbol untuk nama (kunci) dalam objek JSON. Kalau tidak, string dikembalikan. String adalah default.

Doc: Json # parse symbolize_names

Menandai
sumber
symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)adalah cara yang cukup KERING (tetapi tidak efisien) untuk dengan cepat mendapatkan hash dengan kunci bersarang sebagai simbol jika berasal dari file YAML.
Cameron Gagnon
12

params.symbolize_keysjuga akan bekerja. Metode ini mengubah kunci hash menjadi simbol dan mengembalikan hash baru.

Jae Cho
sumber
26
Metode itu bukan inti Ruby. Ini adalah metode Rails.
Ricardo Acras
10

Sebuah modifikasi pada jawaban @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
sumber
Akan sangat membantu jika Anda memasukkan mengapa Anda memodifikasi objek untuk orang yang memindai melalui jawaban.
Dbz
9

Di Rails Anda dapat menggunakan:

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

Mengubah ke:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
JayJay
sumber
3
deep_symbolize_keysditambahkan dalam ekstensi Hash Rails ' , tetapi itu bukan bagian dari inti Ruby.
Ryan Crispin Heneise
7

Ini adalah liner satu saya untuk hash bersarang

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
sumber
FYI, hanya berfungsi untuk Rails. Dalam hal ini, HashWithIndifferentAccess dapat menjadi alternatif yang lebih baik.
Adam Grant
6

Dalam hal alasan Anda perlu melakukan ini adalah karena data Anda awalnya berasal dari JSON, Anda bisa melewatkan parsing ini dengan hanya meneruskan :symbolize_namesopsi setelah menelan JSON.

Tidak diperlukan Rails dan berfungsi dengan Ruby> 1.9

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

Anda bisa malas, dan membungkusnya dalam lambda:

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

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

Tapi ini hanya akan berfungsi untuk membaca dari hash - bukan menulis.

Untuk melakukan itu, Anda bisa menggunakan Hash#merge

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

Blok init akan mengonversi kunci satu kali sesuai permintaan, meskipun jika Anda memperbarui nilai untuk versi string kunci setelah mengakses versi simbol, versi simbol tidak akan diperbarui.

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}

Anda juga bisa memiliki blok init tidak memperbarui hash, yang akan melindungi Anda dari kesalahan semacam itu, tetapi Anda masih rentan terhadap yang sebaliknya - memperbarui versi simbol tidak akan memperbarui versi string:

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}

Jadi hal yang harus diperhatikan adalah beralih di antara dua bentuk utama. Tetap dengan satu.

rampion
sumber
3

Apakah sesuatu seperti pekerjaan berikut?

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

Ini akan menyalin hash, tetapi Anda tidak akan peduli tentang itu sebagian besar waktu. Mungkin ada cara untuk melakukannya tanpa menyalin semua data.

ChrisInEdmonton
sumber
3

fwiw satu-baris yang lebih pendek:

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

Bagaimana dengan ini:

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

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

Ini untuk orang yang menggunakan mrubydan tidak memiliki symbolize_keysmetode apa pun yang ditentukan:

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

Metode:

  • hanya melambangkan tombol yang String
  • jika melambangkan string berarti kehilangan beberapa informasi (menimpa bagian dari hash) meningkatkan a RuntimeError
  • melambangkan hash yang juga mengandung secara rekursif
  • mengembalikan hash yang dilambangkan
  • bekerja di tempat!
Matteo Ragni
sumber
1
Ada kesalahan ketik dalam metode Anda, Anda lupa !masuk symbolize_keys. Jika tidak berfungsi dengan baik.
Ka Mok
1

Array yang ingin kita ubah.

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

Jadikan variabel baru sebagai array kosong sehingga kita dapat ". Menekan" simbol-simbol di.

simbol = []

Di sinilah kita mendefinisikan metode dengan blok.

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

Akhir kode

Jadi ini mungkin cara paling mudah untuk mengonversi string menjadi simbol dalam array Anda di Ruby. Buat array string lalu buat variabel baru dan atur variabel ke array kosong. Kemudian pilih setiap elemen dalam larik pertama yang Anda buat dengan metode ".each". Kemudian gunakan kode blok untuk ".push" semua elemen dalam array baru Anda dan gunakan ".intern or .to_sym" untuk mengonversi semua elemen menjadi simbol.

Simbol lebih cepat karena menyimpan lebih banyak memori dalam kode Anda dan Anda hanya dapat menggunakannya sekali. Simbol yang paling umum digunakan untuk kunci dalam hash yang bagus. Saya bukan programmer ruby ​​terbaik tetapi bentuk kode ini banyak membantu saya. Jika ada yang tahu cara yang lebih baik silakan berbagi dan Anda dapat menggunakan metode ini untuk hash juga!

rubyguest123
sumber
2
Pertanyaannya adalah tentang hash, bukan array.
Richard_G
1

Jika Anda ingin solusi vanila ruby ​​dan karena saya tidak memiliki akses ke ActiveSupportsini adalah solusi melambangkan yang dalam (sangat mirip dengan yang sebelumnya)

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

Dimulai pada Psych 3.0 Anda dapat menambahkan symbolize_names: option

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

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

Catatan: jika Anda memiliki versi Psych lebih rendah dari 3.0 symbolize_names:akan diabaikan secara diam-diam.

Ubuntu 18.04 saya memasukkannya di luar kotak dengan ruby ​​2.5.1p57

Rodrigo Estebanez
sumber
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
sumber
Anda dapat membungkusnya adengan tanda kurung untuk menguraikan argumen blok agar ini menjadi lebih singkat. Lihat jawaban saya misalnya.
Michael Barton
0

Ini bukan persis satu-liner, tetapi mengubah semua kunci string menjadi simbol, juga yang bersarang:

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
Christoffer Joergensen
sumber
0

Saya suka one-liner ini, ketika saya tidak menggunakan Rails, karena saya tidak perlu membuat hash kedua dan menyimpan dua set data saat saya sedang memprosesnya:

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 mengembalikan nilai kunci yang dihapus

nevets138
sumber
0

Facet 'Hash # deep_rekey juga merupakan pilihan yang baik, terutama:

  • jika Anda menemukan gunanya gula lain dari segi dalam proyek Anda,
  • jika Anda lebih suka pembacaan kode lebih dari satu baris cryptical.

Sampel:

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

Dalam ruby ​​saya menemukan ini sebagai cara yang paling sederhana dan mudah dipahami untuk mengubah kunci string dalam hash menjadi simbol:

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

Untuk setiap kunci dalam hash, kita memanggil delete di atasnya yang menghilangkannya dari hash (juga menghapus mengembalikan nilai yang terkait dengan kunci yang telah dihapus) dan kami segera mengatur ini sama dengan kunci yang dilambangkan.


sumber
0

Mirip dengan solusi sebelumnya tetapi ditulis agak berbeda.

  • Ini memungkinkan hash yang bersarang dan / atau memiliki array.
  • Dapatkan konversi kunci menjadi string sebagai bonus.
  • Kode tidak mengubah hash yang dimasukkan.

    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
sumber