Làm cách nào để chuyển đổi một đối tượng String thành đối tượng Hash?


136

Tôi có một chuỗi trông giống như một hàm băm:

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

Làm thế nào để tôi có được một Hash từ nó? giống:

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

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


Tôi nghĩ eval sẽ làm một cái gì đó ở đây. Hãy để tôi kiểm tra trước. Tôi nghĩ rằng câu hỏi quá sớm. :)
Waseem

Ohh yeah chỉ cần vượt qua nó để eval. :)
Waseem

Câu trả lời:


79

Chuỗi được tạo bằng cách gọi Hash#inspectcó thể được biến trở lại thành một hàm băm bằng cách gọi evalnó. 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 hàm băm.

Nếu tôi bắt đầu với hà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 nó trở lại thành hàm 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ả những gì trong hàm băm là chuỗi, ký hiệu, số và mảng, thì nó sẽ hoạt động, bởi vì những thứ đó có biểu diễn chuỗi là cú pháp Ruby hợp lệ.


"nếu tất cả những gì trong hàm băm là chuỗi, ký hiệu và số,". Điều này nói lên 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 evalchọn là hàm 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 được điều đó, bạn cần một trình phân tích cú pháp Ruby đầy đủ hoặc bạn cần biết chuỗi đó đến từ đâu và biết rằng nó chỉ có thể tạo chuỗi, ký hiệu và số. (Xem thêm câu trả lời của Toms Mikoss về việc tin tưởng vào nội dung của chuỗi.)
Ken Bloom

13
Hãy cẩn thận nơi bạn sử dụng này. Sử dụng evalkhông đúng chỗ là một lỗ hổng bảo mật rất lớn. Bất cứ điều gì 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 đó được tiêmrm -fr
Pithikos

153

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

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

2
Câu trả lời này nên được chọn để tránh sử dụng eval.
Michael_Zhang

4
bạn cũng nên thay thế nils, feJSON.parse(hash_as_string.gsub("=>", ":").gsub(":nil,", ":null,"))
Yo Ludke

136

Phương pháp nhanh và bẩn sẽ là

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

Nhưng nó có ý nghĩa bảo mật nghiêm trọng.
Nó thực thi bất cứ thứ gì nó được thông qua, bạn phải chắc chắn 110% (vì, ít nhất là không có người dùng nào ở bất cứ đâu trên đường đi) nó sẽ chỉ chứa các băm được tạo thành đúng cách hoặc các lỗi bất ngờ / sinh vật khủng khiếp từ ngoài vũ trụ có thể bắt đầu bật lên.


16
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

12
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ó. Nguy hiểm ở đây là tương tự như nguy hiểm tiêm SQL. Gsub là thích hợp hơn.
boulder_ruby

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

13
Tôi không thể nhấn mạnh NGUY HIỂM sử dụng EVAL ở đây đủ! Điều này là hoàn toàn bị cấm nếu đầu vào của người dùng có thể đi vào chuỗi của bạn.
Dave Collins

Ngay cả khi bạn nghĩ rằng bạn sẽ không bao giờ mở nó ra công khai hơn, người khác có thể. Tất cả chúng ta (nên) biết cách mã được sử dụng theo những cách bạn không mong đợi. Nó giống như đặt những thứ cực kỳ nặng trên kệ cao, làm cho nó nặng hàng đầu. Bạn không bao giờ nên tạo ra hình thức nguy hiểm này.
Steve Sether

24

Có lẽ YAML.load?


(phương thức tải hỗ trợ chuỗi)
im lặng

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

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

Điều này và to_yaml giải quyết vấn đề của tôi vì tôi có một số quyền kiểm soát cách tạo chuỗi. Cảm ơn!
mlabarca

23

Đoạn mã ngắn này sẽ làm điều đó, nhưng tôi không thể thấy nó hoạt động với hàm 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)

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

VÍ DỤ INPUT: "{: user_id => 11 ,: blog_id => 2 ,: comment_id => 1}" KẾT QUẢ ĐẦU RA: > "1"}


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

3
Điều này cũng sẽ loại bỏ các {}:ký tự khỏi các giá trị bên trong hàm băm được xâu chuỗi?
Vladimir Panteleev

@VladimirPanteleev Bạn nói đúng, nó sẽ như vậy. Bắt tốt đẹp! Bạn có thể thực hiện đánh giá mã của tôi bất cứ ngày nào :)
hrdwdmrbl

20

Các giải pháp cho đến nay bao gồm một số trường hợp nhưng bỏ lỡ một số (xem bên dưới). Đây là nỗ lực của tôi tại một chuyển đổi kỹ lưỡng 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à các ký tự một ký tự được tạo thành từ các ký tự lẻ, nhưng được phép. Ví dụ{:> => :<} là một băm ruby ​​hợp lệ.

Tôi cũng đặt này lên github . Mã này bắt đầu bằng 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


Giải pháp rất tuyệt. Bạn có thể thêm một gsub của tất cả :nilđể :nullđể xử lý mà weirdness cụ thể.
SteveTurczyn

1
Giải pháp này cũng có phần thưởng khi làm việc trên các hàm băm đa cấp theo cách đệ quy, vì nó thúc đẩy phân tích JSON #. Tôi đã có một số rắc rối với lồng trên các giải pháp khác.
Patrick đọc

17

Tôi đã từng gặp vấn đề tương tự. Tôi đã lưu trữ một hàm băm trong Redis. Khi lấy ra hàm băm đó, nó là một chuỗi. Tôi không muốn gọi eval(str)vì lo ngại về an ninh. Giải pháp của tôi là lưu chuỗi băm dưới dạng chuỗi json thay vì chuỗi băm ruby. Nếu bạn có tùy chọn, sử dụng json sẽ 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


1
Đây là câu trả lời tốt nhất cho đến nay. to_jsonJSON.parse
ardochhigh

3
Để bất cứ ai hạ bệ 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 viên 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ố gắng giải quyết vấn đề sai. Tôi nhận ra rằng việc giải quyết câu hỏi được hỏi ở đây là dễ bị lỗi 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 biệt và sử dụng một định dạng được thiết kế để tuần tự hóa và giải tuần tự hóa các đối tượng một cách an toàn. TL; DR: Tôi có cùng một câu hỏi với OP và nhận ra rằng câu trả lời là hỏi 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ó thể tìm hiểu cùng nhau.
Jared Menard

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

1
có downvote nên yêu cầu một lời giải thích và cho thấy ai downvote.
Nick Res

2
Để làm cho câu trả lời này thậm chí có thể áp dụng nhiều hơn cho câu hỏi của OP, nếu biểu diễn chuỗi băm của bạn được gọi là 'strungout', bạn sẽ có thể thực hiện hashit = JSON.parse (strungout.to_json) và sau đó chọn các mục của bạn bên trong hashit thông qua hashit [ 'Tên khóa'] như bình thường.
cixelsyd

11

Tôi thích lạm dụng ActiveSupport :: JSON. Cách tiếp cận của họ là chuyển đổi hàm 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à có lẽ bạn 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 các khóa chuỗi thông thường vì các ký hiệu không phù hợp trong JSON.

Tuy nhiên, nó không thể xử lý các giá trị băm có chuỗi ngày trong đó (chuỗi ngày của chúng tôi không bị bao quanh bởi các chuỗi, đó là vấn đề lớn xảy ra):

chuỗi = '{' last_Vquest_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 nó cố phân tích giá trị ngày.

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


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

9

hoạt động trong đường ray 4.1 và các ký hiệu hỗ trợ không có dấu ngoặc kép {: a => 'b'}

chỉ cần thêm nó 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

Hoạt động trên dòng lệnh, nhưng tôi nhận được "stack stack to deep" khi tôi đặt nó trong một intializer ...
Alex Edelstein

2

Tôi đã xây dựng một hash_parser đá quý để kiểm tra xem băm có an toàn hay không sử dụng ruby_parsergem. Chỉ sau đó, 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 bài kiểm tra trong https://github.com/bibstha/ruby_hash_parser/blob/master/test/test_hash_parser.rb cung cấp cho bạn thêm ví dụ về những điều tôi đã kiểm tra để đảm bảo eval an toàn.


2

Hãy xem xét giải pháp này. Thư viện + thông số:

Tập tin 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

Tập tin 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

1
it "generally works" nhưng không nhất thiết? Tôi sẽ dài dòng hơn trong các bài kiểm tra. it "converts strings to object" { expect('...').to eql ... } it "supports nested objects" { expect('...').to eql ... }
Lex

Xin chào @Lex, phương thức nào được mô tả trong bình luận RubyDoc của nó. Bài kiểm tra tốt hơn không nêu lại, nó sẽ tạo ra các chi tiết không cần thiết dưới dạng văn bản thụ động. Vì vậy, "nói chung hoạt động" là một công thức tốt đẹp để nói rằng công cụ đó, tốt, thường hoạt động. Chúc mừng!
Alex Fortuna

Vâng, vào cuối ngày, bất cứ điều gì làm việc. Bất kỳ bài kiểm tra nào tốt hơn không có bài kiểm tra. Cá nhân tôi là một fan hâm mộ của các mô tả rõ ràng, nhưng đó chỉ là một sở thích.
Lex

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 mình trong trường hợp nó giúp được ai đó. Hoạt động cho một chuỗi chỉ có một độ sâu cấp và các giá trị trống có thể (nhưng không phải là 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]}

0

Chạy qua một vấn đề tương tự cần sử dụng eval ().

Tình hình của tôi, tôi đã lấy một số dữ liệu từ một API và ghi nó vào một tệp cục bộ. Sau đó có thể lấy dữ liệu từ tệp và sử dụng Hash.

Tôi đã sử dụng IO.read () để đọc nội dung của tệp thành một biến. Trong trường hợp này IO.read () tạo nó dưới dạng Chuỗi.

Sau đó, sử dụng eval () để chuyển đổi chuỗi thành 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

Cũng chỉ để đề cập rằng IO là tổ tiên của Tệp. Vì vậy, bạn cũng có thể sử dụng File.read thay thế nếu bạn muốn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.