如何将String对象转换为Hash对象?

时间:2009-11-03 14:24:21

标签: ruby

我有一个看起来像哈希的字符串:

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

字符串可以有任何嵌套深度。它具有在Ruby中键入有效Hash的所有属性。

13 个答案:

答案 0 :(得分:129)

快速而肮脏的方法

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

但它具有严重的安全隐患 它执行它传递的任何东西,你必须110%肯定(因为,至少没有用户输入的任何地方)它只包含正确形成的哈希或来自外太空的意外错误/可怕的生物可能会开始弹出。

答案 1 :(得分:122)

对于不同的字符串,您可以在不使用危险的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('=>', ':')

答案 2 :(得分:76)

通过调用Hash#inspect创建的字符串可以通过调用eval来转换回哈希值。但是,这要求散列中的所有对象都是如此。

如果我从哈希{:a => Object.new}开始,那么它的字符串表示为"{:a=>#<Object:0x7f66b65cf4d0>}",我不能使用eval将其转回哈希,因为#<Object:0x7f66b65cf4d0>是无效的Ruby语法。

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

答案 3 :(得分:22)

这个简短的小片段会做到,但我看不到它使用嵌套哈希。

我认为这很可爱
STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge)

步骤 我删除了'{','}'和':' 我在任何地方找到一个','分开了字符串。 3.只要找到'=&gt;',我就会拆分用拆分创建的每个子串。然后,我创建一个哈希,我刚刚分开的哈希的两边。 我留下了一系列哈希,然后我将它们合并在一起。

示例输入:“{:user_id =&gt; 11,:blog_id =&gt; 2,:comment_id =&gt; 1}” 结果输出:{“user_id”=&gt;“11”,“blog_id”=&gt;“2”,“comment_id”=&gt;“1”}

答案 4 :(得分:20)

也许是YAML.load?

答案 5 :(得分:16)

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

我把这个code up on github as well。此代码以测试字符串开头,以执行所有转换

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)

以下是其他解决方案的一些注释

答案 6 :(得分:14)

我遇到了同样的问题。我在Redis中存储哈希。检索该哈希时,它是一个字符串。出于安全考虑,我不想打电话给eval(str)。我的解决方案是将散列保存为json字符串而不是ruby散列字符串。如果你有选择,使用json更容易。

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

TL; DR:使用to_jsonJSON.parse

答案 7 :(得分:11)

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

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

但是,它无法处理其中包含日期字符串的哈希值(我们的日期字符串最终没有被字符串包围,这是大问题的来源):

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

尝试解析日期值时,会导致无效的JSON字符串错误。

会喜欢有关如何处理此案例的任何建议

答案 8 :(得分:8)

适用于rails 4.1并支持不带引号的符号{:a =&gt; 'B'}

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

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

答案 9 :(得分:2)

我构建了一个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中的测试为您提供了更多我已经过测试以确保eval安全的事例。

答案 10 :(得分:1)

我为此目的写了一个单行后来到这个问题,所以我分享我的代码,以防它帮助某人。适用于只有一个级别深度和可能的空值(但不是零)的字符串,如:

"{ :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]}

答案 11 :(得分:0)

请考虑此解决方案。库+规格:

档案: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

档案: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

答案 12 :(得分:0)

遇到一个类似的问题,需要使用eval()。

我的情况是,我正在从API中提取一些数据并将其写入本地文件中。然后能够从文件中提取数据并使用哈希。

我使用IO.read()将文件的内容读取到一个变量中。在这种情况下,IO.read()将其创建为字符串。

然后使用eval()将字符串转换为哈希。

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

还要提及IO是File的祖先。因此,您也可以根据需要使用File.read。