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 2009年

ええ、それを評価に渡します。:)
Waseem 2009年

回答:


79

呼び出しによって作成された文字列は、呼び出しHash#inspectてハッシュに戻すことができますeval。ただし、これはハッシュ内のすべてのオブジェクトについて同じであることを必要とします。

ハッシュから始める場合{:a => Object.new}、その文字列表現は"{:a=>#<Object:0x7f66b65cf4d0>}"であり、有効なRuby構文ではないevalため、ハッシュに戻すために使用すること#<Object:0x7f66b65cf4d0>はできません。

ただし、ハッシュに含まれるものがすべて文字列、記号、数値、および配列である場合は、Rubyの構文として有効な文字列表現が含まれているため、機能します。


「もしハッシュにあるものがすべて文字列、記号、そして数字なら」。これは多くを言います。したがってeval、上記のステートメントがその文字列に対して有効であることを確認することにより、ハッシュとしてuatedされる文字列の有効性を確認できます。
Waseem、2009年

1
はい。ただし、そのためには完全なRubyパーサーが必要か、最初に文字列がどこから来たのかを知り、文字列、記号、数値しか生成できないことを知っている必要があります。(文字列の内容を信頼することに関するトムズミコスの回答も参照してください。)
ケンブルーム

13
これをどこで使うか注意してください。eval間違った場所での使用は、大きなセキュリティホールです。文字列内のすべてが評価されます。APIで誰かが注入したと想像してみてくださいrm -fr
ピティコス

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 2018

4
あなたはまた、FE NILSを交換する必要がありますJSON.parse(hash_as_string.gsub("=>", ":").gsub(":nil,", ":null,"))
ヨLudke

136

迅速かつ汚い方法は

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

しかし、それはセキュリティに深刻な影響を及ぼします。
渡されたものはすべて実行され、110%確実である必要があります(少なくとも、途中でユーザー入力がないように)、適切に形成されたハッシュのみが含まれるか、宇宙からの予期しないバグ/恐ろしい生き物がポップアップし始める可能性があります。


16
軽いサーベルを持っています。私はそれらの生き物や虫の世話をすることができます。:)
Waseem 2009年

12
私の教師によると、ここではEVALを使用するのは危険な場合があります。EvalはRubyコードを取得して実行します。ここでの危険は、SQLインジェクションの危険に類似しています。Gsubが好ましい。
boulder_ruby

9
Davidの先生が正しい理由を示す文字列の例: '{:surprise => "#{system \" rm -rf * \ "}"}'
A. Wilson

13
ここでは、EVALを使用することの危険性を十分に強調することはできません。ユーザー入力が文字列に巻き込まれる可能性がある場合、これは絶対に禁止されています。
デイブコリンズ

あなたがこれをもっと公に開かないだろうと思っていても、誰かがそうするかもしれません。私たちは皆、コードがどのように予期しない方法で使用されるかを知っているはずです(すべきです)。非常に重いものを高い棚に置いて、重くするようなものです。このような形の危険を作成してはいけません。
Steve Sether

24

たぶんYAML.load?


(loadメソッドは文字列をサポート)
サイレント

5
それにはまったく異なる文字列表現が必要ですが、はるかに安全です。(そして、文字列表現は生成するのと同じくらい簡単です-#inspectではなく#to_yamlを呼び出すだけです)
Ken Bloom

ワオ。yamlで文字列を解析するのがとても簡単だとは思いませんでした。データを生成するLinux bashコマンドチェーンを使用して、任意の文字列形式のマッサージなしでインテリジェントにRubyハッシュに変換します。
ラビリンス

これとto_yamlは、文字列の生成方法をある程度制御できるため、問題を解決します。ありがとう!
mlabarca

23

この短い小さなスニペットはそれを行いますが、ネストされたハッシュで動作することを私は見ることができません。かなりかわいいと思います

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

手順1. '{'、 '}'と ':'を削除します。2. '、'が見つかった場所で文字列を分割します。3.見つかった場合はいつでも、分割で作成された各部分文字列を分割します。 「=>」。次に、分割したハッシュの2つの面でハッシュを作成します。4.ハッシュの配列が残り、それをマージします。

入力例: "{:user_id => 11、:blog_id => 2、:comment_id => 1}"結果出力:{"user_id" => "11"、 "blog_id" => "2"、 "comment_id" = > "1"}


1
それは病気のワンライナーです!:) +1
blushrt 2013年

3
これも文字列化されたハッシュ内のから{}:文字を削除しませんか?
Vladimir Panteleev 2014

@VladimirPanteleevその通りです。ナイスキャッチ!あなたはいつでも私のコードレビューを行うことができます:)
hrdwdmrbl '22

20

これまでの解決策は、いくつかのケースをカバーしていますが、いくつかはありません(以下を参照)。これは、より完全な(安全な)変換の私の試みです。このソリューションでは処理できない1つのコーナーケースを知っています。これは、奇数で構成されている1文字の記号ですが、許可されている文字です。たとえば{:> => :<}、有効なルビハッシュです。

このコードも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 2016年

1
このソリューションは、JSON#parseを利用するため、マルチレベルのハッシュを再帰的に処理するという利点もあります。他のソリューションのネストに問題がありました。
Patrickが2016

17

私も同じ問題を抱えていました。Redisにハッシュを格納していました。そのハッシュを取得するとき、それは文字列でした。eval(str)セキュリティ上の理由で電話をかけたくなかった。私の解決策は、ハッシュをルビハッシュ文字列ではなくjson文字列として保存することでした。オプションがあれば、jsonを使用する方が簡単です。

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

TL; DR:to_jsonJSON.parse


1
これは断然最良の答えです。to_jsonそしてJSON.parse
ardochhigh 2017

3
私に反対票を投じた人に。どうして?同じ問題があり、ルビーハッシュの文字列表現を実際のハッシュオブジェクトに変換しようとしました。間違った問題を解決しようとしていることに気づきました。ここで尋ねた質問を解決することは間違いが起こりやすく、安全ではないことに気づきました。データを別の方法で保存し、オブジェクトを安全にシリアル化および逆シリアル化するように設計された形式を使用する必要があることに気付きました。TL; DR:OPと同じ質問がありましたが、その答えは別の質問をすることであることに気付きました。また、私に反対票を投じた場合は、フィードバックを提供してください。
Jared Menard

3
説明コメントのない反対投票はスタックオーバーフローのガンです。
ardochhigh 2017

1
はい、反対投票には説明が必要で、誰が反対投票するかを示します。
Nick Res

2
この答えをOPの質問にさらに適用するには、ハッシュの文字列表現が「strungout」と呼ばれる場合、hashit = JSON.parse(strungout.to_json)を実行して、hashit [ 'keyname']通常どおり。
cixelsyd

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文字列エラーが発生します。

このケースを処理する方法についての提案があれば歓迎します


2
.decodeへのポインタをありがとう、それは私にとってはうまくいきました。テストするには、JSON応答を変換する必要がありました。ここで私が使用するコードは次のとおりです。ActiveSupport::JSON.decode(response.body, symbolize_keys: true)
アンドリュー・フィリップス

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 を使用していないかどうかを最初にチェックするgem hash_parser作成しましたruby_parser。その後のみ、それが適用され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さん、RubyDocのコメントにメソッドの機能が説明されています。テストはそれを言い直さない方が良いです。パッシブテキストとして不要な詳細を作成します。したがって、「一般的に機能する」とは、一般的に機能することを示すための優れた公式です。乾杯!
Alex Fortuna

ええ、一日の終わりに何がうまくいくか。テストは、テストがないよりも優れています。個人的に私は明示的な説明のファンですが、それは単なる好みです。
Lex

1

この目的のためにワンライナーを書いた後でこの質問に来たので、誰かに役立つ場合に備えて私のコードを共有します。次のように、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()は文字列として作成します。

次に、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を使用することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.