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']


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 

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

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

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]}]

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


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}

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

bagus, saya akan pergi dengan yang ini bahkan jika saya akan menamainya deep_symbolize :)
PierrOz


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


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.


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

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"}}}

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

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)

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.


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.


3

fwiw satu-baris yang lebih pendek:

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

2

Bagaimana dengan ini:

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

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

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!

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!


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

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


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}

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

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


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

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.


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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.