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


136

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

"{ :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中键入有效哈希的所有属性。


我认为eval将在这里做些事情。让我先测试。我认为过早发布了问题。:)
Waseem

哦,是的,只是将其传递给评估。:)
Waseem

Answers:


79

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

如果我以hash开头{:a => Object.new},则其字符串表示形式为"{:a=>#<Object:0x7f66b65cf4d0>}"eval由于#<Object:0x7f66b65cf4d0>无效的Ruby语法,我不能使用它将其转换回hash 。

但是,如果哈希中仅包含字符串,符号,数字和数组,则它应该起作用,因为它们具有有效的Ruby语法的字符串表示形式。


“如果哈希中仅包含字符串,符号和数字,”。这说了很多。因此,eval通过确保上面的语句对于该字符串有效,我可以检查要用作哈希值的字符串的有效性。
Waseem

1
是的,但是要这样做,您要么需要一个完整的Ruby解析器,要么首先需要知道字符串的来源,并且知道它只能生成字符串,符号和数字。(另请参见Toms Mikoss关于信任字符串内容的答复。)
Ken Bloom

13
使用时请当心。eval在错误的地方使用安全带是一个巨大的漏洞。字符串中的所有内容都将被评估。因此,想象一下是否有人在API中注入了rm -fr
Pithikos'Aug

153

对于不同的字符串,您可以不使用危险的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
应该选择此答案以避免使用eval。
Michael_Zhang

4
您也应该替换nils,feJSON.parse(hash_as_string.gsub("=>", ":").gsub(":nil,", ":null,"))
Yo Ludke

136

快速而肮脏的方法是

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

但这具有严重的安全隐患。
它执行所传递的任何内容,您必须确保110%确信(例如,至少在此过程中没有任何用户输入),它将仅包含正确形成的哈希值或来自外层空间的意外错误/可怕的生物可能开始弹出。


16
我随身带了一把军刀。我可以照顾那些生物和虫子。:)
Waseem

12
据我的老师说,在这里使用EVAL可能很危险。Eval会获取任何Ruby代码并运行它。这里的危险类似于SQL注入危险。Gsub是优选的。
boulder_ruby

9
示例字符串,显示David的老师为什么正确:'{:surprise =>“#{system \” rm -rf * \“}”}“
A. Wilson

13
我不能在这里强调使用EVAL的危险!如果用户输入可以缠绕到字符串中,则绝对禁止这样做。
戴夫·柯林斯

即使您认为您永远都不会公开公开此事,也可能会有其他人。我们所有人(应该)都知道如何以您意想不到的方式使用代码。这就像将非常重的东西放在高架子上,使其变得重。您应该永远不要制造这种形式的危险。
Steve Sether

24

也许YAML.load吗?


(加载方法支持字符串)
静默

5
这需要完全不同的字符串表示形式,但是要安全得多。(而且字符串表示形式也很容易生成-只需调用#to_yaml,而不是#inspect)
Ken Bloom

哇。我不知道用yaml解析字符串是如此简单。它采用了我的linux bash命令链来生成数据,并智能地将其转换为ruby Hash,无需任何字符串格式按摩。
迷宫

这和to_yaml解决了我的问题,因为我可以控制字符串的生成方式。谢谢!
mlabarca

23

这个简短的小片段可以做到,但我看不到它可以与嵌套哈希一起使用。我觉得虽然很可爱

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

步骤1.消除'{','}'和':'2.在字符串中找到','的位置均进行拆分一个'=>'。然后,我创建一个哈希,将哈希的两个侧面分开。4.我剩下一系列哈希,然后将它们合并在一起。

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


1
那是一个生病的人!:) +1
blushrt

3
这是否还会{}:从字符串化哈希中的值中去除字符?
弗拉基米尔·潘捷列夫2014年

@VladimirPanteleev你说得对,没错。好赶上!您可以随时进行我的代码审核:)
hrdwdmrbl '19

20

到目前为止,解决方案涵盖了某些情况,但错过了一些情况(请参阅下文)。这是我尝试进行更彻底(安全)的转换的尝试。我知道这种解决方案无法处理的一个特殊情况是由奇数但允许的字符组成的单个字符符号。例如{:> => :<}是有效的红宝石哈希。

我也将此代码放在github上。此代码以测试字符串开头,以执行所有转换

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)

这是这里其他解决方案的一些注意事项


很酷的解决方案。您可以将所有的GSUB添加:nil:null处理特定的怪事。
SteveTurczyn,

1
该解决方案还利用JSON#parse,因此具有递归地处理多级哈希的好处。我在嵌套其他解决方案时遇到了一些麻烦。
帕特里克

17

我有同样的问题。我在Redis中存储了一个哈希。检索该哈希时,它是一个字符串。eval(str)由于安全方面的考虑,我不想打电话。我的解决方案是将哈希另存为json字符串,而不是ruby哈希字符串。如果可以的话,使用json更容易。

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

TL; DR:使用to_jsonJSON.parse


1
到目前为止,这是最好的答案。to_jsonJSON.parse
ardochhigh

3
谁拒绝了我。为什么?我遇到了同样的问题,试图将红宝石哈希的字符串表示形式转换为实际的哈希对象。我意识到自己正在尝试解决错误的问题。我意识到解决这里提出的问题是容易出错和不安全的。我意识到我需要以不同的方式存储数据,并使用一种旨在安全地序列化和反序列化对象的格式。TL; DR:我和OP有相同的问题,并且意识到答案是要提出不同的问题。另外,如果您不赞成我,请提供反馈意见,以便我们大家共同学习。
贾里德·梅纳德

3
不加解释性地拒绝投票是Stack Overflow的风险。
ardochhigh

1
是的,应通过投票解释并说明谁在投票。
Nick Res

2
为了使该答案更适用于OP的问题,如果您将哈希的字符串表示形式称为“ strungout”,则您应该能够进行hashit = JSON.parse(strungout.to_json),然后通过hashit []在hashit中选择项'keyname']正常。
cixelsyd

11

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

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

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

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

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

希望对如何处理此案有任何建议


2
感谢您指向.decode的指针,它对我来说很棒。我需要转换一个JSON响应来对其进行测试。这是我使用的代码:ActiveSupport::JSON.decode(response.body, symbolize_keys: true)
Andrew Philips

9

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

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

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

在命令行上工作,但是当我将它放在初始化器中时,我会得到“栈深”……
Alex Edelstein

2

我建立了一个gem hash_parser,它首先检查哈希是否安全或不使用ruby_parsergem。只有这样,它才会应用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安全而进行了测试的更多示例。


2

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

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

1
it "generally works" 但不一定吗?在那些测试中我会更详细。 it "converts strings to object" { expect('...').to eql ... } it "supports nested objects" { expect('...').to eql ... }
Lex

嘿@Lex,它的RubyDoc注释描述了什么方法。测试最好不要重新声明它,它会创建不必要的细节作为被动文本。因此,“通常有效”是一个很好的公式,可以说明某些东西通常都可以起作用。干杯!
亚历克斯·福图纳

是的,不管怎么说,不管怎么说。任何测试总比没有测试好。我个人是明确描述的粉丝,但是那只是一个偏爱。
Lex

1

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

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

0

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

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

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

然后使用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。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.