Saya memiliki string yang terlihat seperti hash:
"{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }"
Bagaimana cara mendapatkan Hash darinya? Suka:
{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }
String bisa memiliki kedalaman bersarang. Ia memiliki semua properti bagaimana Hash yang valid diketik di Ruby.
Jawaban:
String yang dibuat dengan memanggil
Hash#inspect
dapat diubah kembali menjadi hash dengan memanggilnyaeval
. Namun, ini membutuhkan hal yang sama untuk menjadi benar untuk semua objek dalam hash.Jika saya mulai dengan hash
{:a => Object.new}
, maka representasi stringnya adalah"{:a=>#<Object:0x7f66b65cf4d0>}"
, dan saya tidak dapat menggunakannyaeval
untuk mengubahnya kembali menjadi hash karena#<Object:0x7f66b65cf4d0>
bukan sintaks Ruby yang valid.Namun, jika semua yang ada di hash adalah string, simbol, angka, dan array, itu harus berfungsi, karena mereka memiliki representasi string yang merupakan sintaks Ruby yang valid.
sumber
eval
uated sebagai hash dengan memastikan bahwa pernyataan di atas valid untuk string itu.eval
di tempat yang salah adalah lubang keamanan yang sangat besar. Apa pun yang ada di dalam string, akan dievaluasi. Jadi bayangkan jika dalam API seseorang disuntikkanrm -fr
Untuk string yang berbeda, Anda dapat melakukannya tanpa menggunakan
eval
metode berbahaya :hash_as_string = "{\"0\"=>{\"answer\"=>\"1\", \"value\"=>\"No\"}, \"1\"=>{\"answer\"=>\"2\", \"value\"=>\"Yes\"}, \"2\"=>{\"answer\"=>\"3\", \"value\"=>\"No\"}, \"3\"=>{\"answer\"=>\"4\", \"value\"=>\"1\"}, \"4\"=>{\"value\"=>\"2\"}, \"5\"=>{\"value\"=>\"3\"}, \"6\"=>{\"value\"=>\"4\"}}" JSON.parse hash_as_string.gsub('=>', ':')
sumber
JSON.parse(hash_as_string.gsub("=>", ":").gsub(":nil,", ":null,"))
Metode cepat dan kotor akan
eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }")
Tapi itu memiliki implikasi keamanan yang parah.
Itu mengeksekusi apa pun yang diteruskan, Anda harus yakin 110% (seperti, setidaknya tidak ada input pengguna di mana pun di sepanjang jalan) itu hanya akan berisi hash yang terbentuk dengan benar atau bug yang tidak terduga / makhluk mengerikan dari luar angkasa mungkin mulai bermunculan.
sumber
Mungkin YAML.load?
sumber
Potongan kecil pendek ini akan melakukannya, tetapi saya tidak dapat melihatnya berfungsi dengan hash bersarang. Saya pikir itu cukup lucu
STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge)
Langkah 1. Saya menghilangkan '{', '}' dan ':' 2. Saya membagi string di mana pun ia menemukan ',' 3. Saya membagi setiap substring yang dibuat dengan split, setiap kali ditemukan a '=>'. Kemudian, saya membuat hash dengan kedua sisi hash yang baru saja saya pisahkan. 4. Saya memiliki array hashes yang kemudian saya gabungkan.
CONTOH INPUT: "{: user_id => 11,: blog_id => 2,: comment_id => 1}" HASIL KELUARAN: {"user_id" => "11", "blog_id" => "2", "comment_id" = > "1"}
sumber
{}:
karakter dari nilai di dalam hash yang dirangkai?Solusi sejauh ini mencakup beberapa kasus tetapi melewatkan beberapa (lihat di bawah). Inilah upaya saya pada konversi yang lebih menyeluruh (aman). Saya tahu satu kasus sudut yang tidak ditangani oleh solusi ini yang merupakan simbol karakter tunggal yang terdiri dari karakter ganjil, tetapi diperbolehkan. Misalnya
{:> => :<}
adalah hash ruby yang valid.Saya memasang kode ini di github juga . Kode ini dimulai dengan string pengujian untuk menjalankan semua konversi
require 'json' # Example ruby hash string which exercises all of the permutations of position and type # See http://json.org/ ruby_hash_text='{"alpha"=>{"first second > third"=>"first second > third", "after comma > foo"=>:symbolvalue, "another after comma > foo"=>10}, "bravo"=>{:symbol=>:symbolvalue, :aftercomma=>10, :anotheraftercomma=>"first second > third"}, "charlie"=>{1=>10, 2=>"first second > third", 3=>:symbolvalue}, "delta"=>["first second > third", "after comma > foo"], "echo"=>[:symbol, :aftercomma], "foxtrot"=>[1, 2]}' puts ruby_hash_text # Transform object string symbols to quoted strings ruby_hash_text.gsub!(/([{,]\s*):([^>\s]+)\s*=>/, '\1"\2"=>') # Transform object string numbers to quoted strings ruby_hash_text.gsub!(/([{,]\s*)([0-9]+\.?[0-9]*)\s*=>/, '\1"\2"=>') # Transform object value symbols to quotes strings ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>\s*:([^,}\s]+\s*)/, '\1\2=>"\3"') # Transform array value symbols to quotes strings ruby_hash_text.gsub!(/([\[,]\s*):([^,\]\s]+)/, '\1"\2"') # Transform object string object value delimiter to colon delimiter ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>/, '\1\2:') puts ruby_hash_text puts JSON.parse(ruby_hash_text)
Berikut beberapa catatan tentang solusi lain di sini
eval
yang terlalu menakutkan bagi saya (seperti yang ditunjukkan Toms dengan benar).sumber
:nil
untuk:null
ke pegangan yang keanehan tertentu.Saya memiliki masalah yang sama. Saya menyimpan hash di Redis. Saat mengambil hash itu, itu adalah string. Saya tidak ingin menelepon
eval(str)
karena masalah keamanan. Solusi saya adalah menyimpan hash sebagai string json, bukan string hash ruby. Jika Anda memiliki opsi, menggunakan json lebih mudah.TL; DR: gunakan
to_json
danJSON.parse
sumber
to_json
danJSON.parse
Saya lebih suka menyalahgunakan ActiveSupport :: JSON. Pendekatan mereka adalah dengan mengubah hash ke yaml dan kemudian memuatnya. Sayangnya konversi ke yaml tidak sederhana dan Anda mungkin ingin meminjamnya dari AS jika Anda belum memiliki AS dalam proyek Anda.
Kami juga harus mengubah simbol apa pun menjadi kunci string biasa karena simbol tidak sesuai di JSON.
Namun, itu tidak dapat menangani hash yang memiliki string tanggal di dalamnya (string tanggal kami akhirnya tidak dikelilingi oleh string, di situlah masalah besar masuk):
string = '{' last_request_at ': 2011-12-28 23:00:00 UTC}'
ActiveSupport::JSON.decode(string.gsub(/:([a-zA-z])/,'\\1').gsub('=>', ' : '))
Akan menghasilkan kesalahan string JSON yang tidak valid ketika mencoba mengurai nilai tanggal.
Akan sangat senang jika ada saran tentang cara menangani kasus ini
sumber
ActiveSupport::JSON.decode(response.body, symbolize_keys: true)
berfungsi di rel 4.1 dan mendukung simbol tanpa tanda kutip {: a => 'b'}
cukup tambahkan ini ke folder penginisialisasi:
class String def to_hash_object JSON.parse(self.gsub(/:([a-zA-z]+)/,'"\\1"').gsub('=>', ': ')).symbolize_keys end end
sumber
Harap pertimbangkan solusi ini. Perpustakaan + spesifikasi:
Berkas
lib/ext/hash/from_string.rb
::require "json" module Ext module Hash module ClassMethods # Build a new object from string representation. # # from_string('{"name"=>"Joe"}') # # @param s [String] # @return [Hash] def from_string(s) s.gsub!(/(?<!\\)"=>nil/, '":null') s.gsub!(/(?<!\\)"=>/, '":') JSON.parse(s) end end end end class Hash #:nodoc: extend Ext::Hash::ClassMethods end
Berkas
spec/lib/ext/hash/from_string_spec.rb
::require "ext/hash/from_string" describe "Hash.from_string" do it "generally works" do [ # Basic cases. ['{"x"=>"y"}', {"x" => "y"}], ['{"is"=>true}', {"is" => true}], ['{"is"=>false}', {"is" => false}], ['{"is"=>nil}', {"is" => nil}], ['{"a"=>{"b"=>"c","ar":[1,2]}}', {"a" => {"b" => "c", "ar" => [1, 2]}}], ['{"id"=>34030, "users"=>[14105]}', {"id" => 34030, "users" => [14105]}], # Tricky cases. ['{"data"=>"{\"x\"=>\"y\"}"}', {"data" => "{\"x\"=>\"y\"}"}], # Value is a `Hash#inspect` string which must be preserved. ].each do |input, expected| output = Hash.from_string(input) expect([input, output]).to eq [input, expected] end end # it end
sumber
it "generally works"
tapi belum tentu? Saya akan lebih bertele-tele dalam tes tersebut.it "converts strings to object" { expect('...').to eql ... }
it "supports nested objects" { expect('...').to eql ... }
Saya membangun gem hash_parser yang pertama kali memeriksa apakah hash aman atau tidak digunakan
ruby_parser
gem. Hanya kemudian, itu berlakueval
.Anda dapat menggunakannya sebagai
require 'hash_parser' # this executes successfully a = "{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }" p HashParser.new.safe_load(a) # this throws a HashParser::BadHash exception a = "{ :key_a => system('ls') }" p HashParser.new.safe_load(a)
Pengujian di https://github.com/bibstha/ruby_hash_parser/blob/master/test/test_hash_parser.rb memberi Anda lebih banyak contoh hal-hal yang telah saya uji untuk memastikan eval aman.
sumber
Saya sampai pada pertanyaan ini setelah menulis satu baris untuk tujuan ini, jadi saya membagikan kode saya seandainya itu membantu seseorang. Berfungsi untuk string dengan hanya satu level kedalaman dan kemungkinan nilai kosong (tapi tidak nihil), seperti:
"{ :key_a => 'value_a', :key_b => 'value_b', :key_c => '' }"
Kodenya adalah:
the_string = '...' the_hash = Hash.new the_string[1..-2].split(/, /).each {|entry| entryMap=entry.split(/=>/); value_str = entryMap[1]; the_hash[entryMap[0].strip[1..-1].to_sym] = value_str.nil? ? "" : value_str.strip[1..-2]}
sumber
Menemukan masalah serupa yang perlu menggunakan eval ().
Situasi saya, saya menarik beberapa data dari API dan menulisnya ke file secara lokal. Kemudian bisa menarik data dari file dan menggunakan Hash.
Saya menggunakan IO.read () untuk membaca konten file menjadi variabel. Dalam hal ini IO.read () membuatnya sebagai String.
Kemudian digunakan eval () untuk mengubah string menjadi Hash.
read_handler = IO.read("Path/To/File.json") puts read_handler.kind_of?(String) # Returns TRUE a = eval(read_handler) puts a.kind_of?(Hash) # Returns TRUE puts a["Enter Hash Here"] # Returns Key => Values puts a["Enter Hash Here"].length # Returns number of key value pairs puts a["Enter Hash Here"]["Enter Key Here"] # Returns associated value
Juga hanya untuk menyebutkan bahwa IO adalah nenek moyang File. Jadi Anda juga bisa menggunakan File.read jika Anda mau.
sumber