2009-11-03 64 views
101

Tôi có một chuỗi mà trông giống như một băm:Làm thế nào để chuyển đổi một đối tượng String thành một đối tượng Hash?

"{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }" 

Làm thế nào để có được một Hash ra khỏi nó? như:

{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } } 

Chuỗi có thể có độ sâu làm tổ. Nó có tất cả các thuộc tính như thế nào một Hash hợp lệ được gõ vào Ruby.

+0

Tôi nghĩ rằng eval sẽ làm điều gì đó ở đây. Hãy để tôi kiểm tra đầu tiên. Tôi đã đăng câu hỏi quá sớm. :) – Waseem

+0

Ohh vâng, chỉ cần chuyển nó đến eval. :) – Waseem

Trả lời

66

Chuỗi được tạo bằng cách gọi Hash#inspect có thể được chuyển trở lại thành băm bằng cách gọi eval trên đó. Tuy nhiên, điều này đòi hỏi phải giống với tất cả các đối tượng trong băm.

Nếu tôi bắt đầu bằng mã băm {:a => Object.new}, thì biểu diễn chuỗi của nó là "{:a=>#<Object:0x7f66b65cf4d0>}" và tôi không thể sử dụng eval để biến trở lại thành băm vì #<Object:0x7f66b65cf4d0> không phải là cú pháp Ruby hợp lệ.

Tuy nhiên, nếu tất cả trong băm là chuỗi, ký hiệu, số và mảng, thì nó sẽ hoạt động, bởi vì chúng có biểu diễn chuỗi là cú pháp Ruby hợp lệ.

+0

"nếu tất cả trong băm là chuỗi, ký hiệu và số,". Điều này nói rất nhiều. Vì vậy, tôi có thể kiểm tra tính hợp lệ của một chuỗi được 'eval'uated dưới dạng băm bằng cách đảm bảo rằng câu lệnh trên là hợp lệ cho chuỗi đó. – Waseem

+1

Có, nhưng để làm điều đó bạn cần một trình phân tích cú pháp đầy đủ của Ruby, hoặc bạn cần phải biết chuỗi xuất phát từ đâu và biết rằng nó chỉ có thể tạo chuỗi, ký hiệu và số. (Xem thêm Toms Mikoss của câu trả lời về tin tưởng các nội dung của chuỗi.) –

+6

Hãy carefule nơi bạn sử dụng này. Sử dụng 'eval' ở sai chỗ là một lỗ hổng bảo mật lớn. Mọi thứ bên trong chuỗi, sẽ được đánh giá. Vì vậy, hãy tưởng tượng nếu trong một API ai đó tiêm 'rm -fr' – Pithikos

116

phương pháp nhanh chóng và bẩn sẽ

eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }") 

Nhưng nó có ý nghĩa quan an ninh nghiêm trọng.
Nó thực hiện bất cứ điều gì được truyền, bạn phải chắc chắn 110% (như trong, ít nhất là không có người dùng nhập vào bất cứ nơi nào trên đường đi) nó sẽ chỉ chứa băm được tạo đúng hoặc lỗi không mong muốn/sinh vật khủng khiếp từ không gian bên ngoài có thể bắt đầu xuất hiện.

+10

Tôi có một thanh kiếm ánh sáng với tôi. Tôi có thể chăm sóc những sinh vật và lỗi đó. :) – Waseem

+10

SỬ DỤNG EVAL có thể nguy hiểm ở đây, theo giáo viên của tôi. Eval lấy bất kỳ mã ruby ​​nào và chạy nó. Sự nguy hiểm ở đây tương tự như nguy cơ tiêm SQL. Gsub là thích hợp hơn. –

+6

Chuỗi ví dụ cho thấy lý do tại sao giáo viên của David là chính xác: '{: surprise => "# {system \" rm -rf * \ "}"}' –

17

Có thể YAML.load?

+0

(phương pháp tải hỗ trợ dây) – silent

+4

Điều đó đòi hỏi một biểu diễn chuỗi hoàn toàn khác nhau, nhưng nó an toàn hơn nhiều. (Và biểu diễn chuỗi cũng dễ tạo ra - chỉ cần gọi #to_yaml, thay vì #inspect) –

+0

Wow. Tôi không có ý tưởng nó rất dễ dàng để phân tích chuỗi w/yaml. Nó có chuỗi lệnh bash linux của tôi tạo ra dữ liệu và thông minh biến nó thành một ruby ​​Hash w/o bất kỳ định dạng chuỗi nào xoa bóp. – labyrinth

1

Tôi đã đến câu hỏi này sau khi viết một lớp lót cho mục đích này, vì vậy tôi chia sẻ mã của tôi trong trường hợp nó giúp ai đó. Làm việc cho một chuỗi với chỉ có một chiều sâu mực và giá trị sản phẩm nào có thể (nhưng không phải không), như:

"{ :key_a => 'value_a', :key_b => 'value_b', :key_c => '' }" 

Mã này là:

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

Tôi thích lạm dụng ActiveSupport :: JSON. Cách tiếp cận của họ là chuyển đổi băm thành yaml và sau đó tải nó. Thật không may việc chuyển đổi sang yaml không đơn giản và bạn có thể muốn mượn nó từ AS nếu bạn chưa có AS trong dự án của mình.

Chúng tôi cũng phải chuyển đổi bất kỳ ký hiệu nào thành chuỗi khóa thông thường vì biểu tượng không phù hợp trong JSON.

Tuy nhiên, không thể của nó để xử lý băm rằng có một chuỗi ngày trong họ (chuỗi ngày của chúng tôi kết thúc không được bao quanh bởi dây, đó là nơi mà các vấn đề lớn do thỏa thuận):

string = '{' last_request_at ': 2011-12-28 23:00:00 UTC}' ActiveSupport::JSON.decode(string.gsub(/:([a-zA-z])/,'\\1').gsub('=>', ' : '))

Sẽ dẫn đến lỗi chuỗi JSON không hợp lệ khi tìm cách phân tích cú pháp giá trị ngày tháng.

Rất thích bất kỳ đề xuất về cách xử lý trường hợp này

+2

Cảm ơn con trỏ tới .decode, nó hoạt động rất tốt cho tôi. Tôi cần chuyển đổi một câu trả lời JSON để kiểm tra nó. Đây là mã tôi đã sử dụng: 'ActiveSupport :: JSON.decode (response.body, symbolize_keys: true)' –

19

đoạn nhỏ ngắn này sẽ làm điều đó, nhưng tôi không thể nhìn thấy nó làm việc với một băm lồng nhau.Tôi nghĩ rằng nó khá dễ thương mặc dù

STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge) 

bước 1. Tôi loại bỏ '{', '}' và ':' 2. Tôi chia theo chuỗi bất cứ nơi nào mà nó tìm thấy một '' 3. Tôi chia từng phần nền được tạo ra với sự phân chia, bất cứ khi nào nó tìm thấy một '=>'. Sau đó, tôi tạo ra một băm với hai bên của băm tôi chỉ tách ra. 4. Tôi còn lại với một mảng băm mà sau đó tôi hợp nhất với nhau.

VÍ DỤ VÍ DỤ: "{: user_id => 11,: blog_id => 2,: comment_id => 1}" RESULT OUTPUT: {"user_id" => "11", "blog_id" => "2" "comment_id" => "1"}

+1

Đó là một oneliner bị bệnh! :) +1 – blushrt

+2

Liệu điều này cũng không xóa bỏ '{}:' các ký tự từ * các giá trị * bên trong hàm băm được xâu chuỗi? –

88

Đối với chuỗi khác nhau, bạn có thể làm điều đó mà không sử dụng nguy hiểm eval phương pháp:

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('=>', ':') 
7

công trình đường ray 4.1 và biểu tượng hỗ trợ mà không có dấu ngoặc kép {: a => ' b '}

chỉ cần thêm thư mục này vào thư mục khởi tạo:

class String 
    def to_hash_object 
    JSON.parse(self.gsub(/:([a-zA-z]+)/,'"\\1"').gsub('=>', ': ')).symbolize_keys 
    end 
end 
+0

Hoạt động trên dòng lệnh, nhưng tôi nhận được "stack level to deep" khi tôi đặt cái này vào bộ phận intializer ... –

14

Các giải pháp cho đến nay bao gồm một số trường hợp nhưng bỏ sót một số trường hợp (xem bên dưới). Đây là nỗ lực của tôi trong một chuyển đổi toàn diện hơn (an toàn). Tôi biết một trường hợp góc mà giải pháp này không xử lý đó là ký tự ký tự đơn tạo thành các ký tự lẻ, nhưng được cho phép. Ví dụ: {:> => :<} là băm băm hợp lệ.

Tôi đặt số này code up on github as well. Mã này bắt đầu với một chuỗi thử nghiệm để thực hiện tất cả các chuyển đổi

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) 

Dưới đây là một số lưu ý về các giải pháp khác ở đây

+0

Giải pháp rất tuyệt. Bạn có thể thêm một gsub của tất cả ': nil' vào': null' để xử lý sự kỳ quặc đặc biệt đó. – SteveTurczyn

+0

Giải pháp này cũng có tiền thưởng làm việc trên băm đa cấp đệ quy, vì nó sử dụng JSON # parse. Tôi gặp rắc rối với việc lồng ghép vào các giải pháp khác. –

0

Vui lòng xem xét giải pháp này. Thư viện + spec:

File: 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"}') 
     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 

File: spec/lib/ext/hash/from_string_spec.rb:

require "ext/hash/from_string" 
require "rspec/match_result" # Get from https://github.com/dadooda/rspec_match_result. 

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| 
     match_result(input, expected) {|input| Hash.from_string(input)} 
    end 
    end # it 
end 
7

tôi đã cùng một vấn đề. Tôi đã lưu trữ một băm trong Redis. Khi lấy băm đó, nó là một chuỗi. Tôi không muốn gọi số eval(str) vì lo ngại về bảo mật. Giải pháp của tôi là để lưu băm như một chuỗi json thay vì một chuỗi băm ruby.Nếu bạn có tùy chọn, sử dụng json dễ dàng hơn.

redis.set(key, ruby_hash.to_json) 
    JSON.parse(redis.get(key)) 

TL; DR: sử dụng to_jsonJSON.parse

+0

Đây là câu trả lời hay nhất cho đến nay. '' 'to_json''' và' '' JSON.parse''' – ardochhigh

+2

Cho bất kỳ ai đã bỏ phiếu cho tôi. Tại sao? Tôi đã có cùng một vấn đề, cố gắng để chuyển đổi một chuỗi đại diện của một băm ruby ​​thành một đối tượng băm thực tế. Tôi nhận ra rằng tôi đang cố giải quyết vấn đề sai. Tôi nhận ra rằng việc giải quyết các câu hỏi được hỏi ở đây là lỗi dễ bị và không an toàn. Tôi nhận ra rằng tôi cần lưu trữ dữ liệu của mình một cách khác nhau và sử dụng định dạng được thiết kế để tuần tự và sắp xếp một cách an toàn các đối tượng. TL; DR: Tôi có câu hỏi giống như OP, và nhận ra rằng câu trả lời là đặt một câu hỏi khác. Ngoài ra, nếu bạn bỏ phiếu cho tôi, vui lòng cung cấp phản hồi để tất cả chúng ta cùng nhau học hỏi. –

+1

Downvoting mà không có một bình luận giải thích là ung thư của Stack tràn. – ardochhigh

0

tôi đã xây dựng một viên ngọc hash_parser rằng séc đầu tiên nếu một hash có an toàn hay không sử dụng ruby_parser đá quý. Chỉ khi đó, nó áp dụng eval.

Bạn có thể sử dụng nó như

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) 

Các thử nghiệm trong https://github.com/bibstha/ruby_hash_parser/blob/master/test/test_hash_parser.rb cung cấp cho bạn nhiều ví dụ về những điều tôi đã thử nghiệm để đảm bảo eval là an toàn.

Các vấn đề liên quan