如何将一个String对象转换成一个Hash对象?

我有一个string,看起来像一个哈希:

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

我如何得到一个哈希? 喜欢:

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

string可以有任何深度的嵌套。 它具有如何在Ruby中input有效的Hash的所有属性。

通过调用Hash#inspect创build的string可以通过调用eval来变成哈希值。 但是,这要求散列中的所有对象都是相同的。

如果我以hash {:a => Object.new} ,那么它的string表示forms是"{:a=>#<Object:0x7f66b65cf4d0>}" ,我不能用eval把它变成一个散列因为#<Object:0x7f66b65cf4d0>不是有效的Ruby语法。

但是,如果散列中的所有内容都是string,符号,数字和数组,那么它应该可以工作,因为它们具有有效的Ruby语法的string表示forms。

快速和肮脏的方法将是

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

但它有严重的安全隐患。
它执行任何它传递的信息,你必须有110%的可信度(至less在用户input的时候没有任何用户input),只能包含正确形成的哈希值或来自外太空的意外错误/恐怖生物可能开始popup。

对于不同的string,你可以不使用危险的eval方法:

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

这个简短的小代码片段就可以做到,但是我看不到它使用嵌套散列。 我觉得这很可爱

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

步骤1.删除'{','}'和':'2.我在string上find一个','3.我将拆分创build的每个子string拆分,每当发现a'=>'。 然后,我创build一个散列,我只是分开散列的两侧。 来自“简明英汉词典”我剩下一堆哈希,然后合并在一起。

示例input:“{:user_id => 11,:blog_id => 2,:comment_id => 1}”RESULT OUTPUT:{“user_id”=>“11”,“blog_id”=>“2”,“comment_id”= > “1”}

也许YAML.load?

到目前为止的解决scheme涵盖了一些情况,但错过了一些(见下文)。 这是我尝试更彻底(安全)的转换。 我知道这个解决scheme没有处理的一个angular落案例是由单个字符组成的单个字符符号,但是允许字符。 例如{:> => :<}是一个有效的ruby散列。

我也把这个代码放在github上 。 此代码以testingstring开始,以执行所有转换

 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) 

这里有一些关于其他解决scheme的说明

  • @ Ken Bloom和@Toms Mikoss的解决scheme使用的eval对我来说太可怕了(正如Toms正确地指出的那样)。
  • 如果您的哈希没有符号或数字键, @ zolter的解决scheme工作。
  • @jackquack的解决scheme工作,如果没有引用的string与符号混合。
  • 如果您的符号不使用所有允许的字符( 符号文字具有更广泛的允许字符集),那么@Eugene的解决scheme就可以工作。
  • @Pablo的解决scheme ,只要你没有符号和引用string的混合。

我更喜欢滥用ActiveSupport :: JSON。 他们的做法是将散列转换为yaml,然后加载它。 不幸的是,转换yaml并不简单,如果你的项目中没有AS,你可能想从AS借用它。

我们还必须将任何符号转换为常规string键,因为符号在JSON中不合适。

然而,它无法处理在其中有datestring的哈希值(我们的datestring最终没有被string包围,这是大问题出现的地方):

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

当它试图分析date值会导致无效的JSONstring错误。

会爱如何处理这个案件的任何build议

在rails 4.1中工作,并支持不带引号的符号{:a =>'b'}

只需将其添加到初始化文件夹:

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

我有同样的问题。 我在Redis中存储了一个散列。 当检索该散列时,它是一个string。 由于安全考虑,我不想调用eval(str) 。 我的解决scheme是将散列保存为一个JSONstring,而不是一个ruby散列string。 如果你有select,使用json更容易。

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

TL; DR:使用to_jsonJSON.parse

为了这个目的,我写了一行代码后才想到这个问题,所以我分享我的代码以防万一。 适用于只有一层深度和可能的空值(但不为零)的string,如:

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

代码是:

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

请考虑这个解决scheme。 库+规格:

文件: 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 

文件: 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 

我构build了一个gem hash_parser ,首先使用ruby_parser gem检查散列是否安全。 只有这样,才会应用eval

你可以使用它

 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) 

https://github.com/bibstha/ruby_hash_parser/blob/master/test/test_hash_parser.rb中的testing给你更多的例子,我已经testing,以确保eval是安全的。;