結論
eval
を使えば大体できる。使い所はtestコード
。
背景
ハッシュを文字列にしたログの出力結果をminitestで検証しようとした時に、文字列をハッシュに変換し、各項目に正しく値が入っていることを確認しようとした時でした。
文字列に対してto_hashしようとしてもエラーになる。
ハッシュオブジェクトにto_s
すると文字列になるのに、文字列に対してto_hash
するとエラーになります。
どうすれば文字列をハッシュに変換できるか、私なりに考えてみました。
evalを使う
evalを利用すると簡単に文字列をハッシュに変換することができます。
hoge_hash = { key1: 'hogehoge', key2: 999 }
# => {:key1=>"hogehoge", :key2=>999}
hoge_string = hoge_hash.to_s
# => "{:key1=>\"hogehoge\", :key2=>999}"
eval(hoge_string)
# => {:key1=>"hogehoge", :key2=>999}
以上で大体の文字列からハッシュへの変換は簡単にできます。
しかし次のようなハッシュの場合はどうでしょうか?
hoge_hash = { key1: Time.current, key2: Date.today }
# => {:key1=>Tue, 11 Jun 2024 09:33:55.541579252 JST +09:00, :key2=>Tue, 11 Jun 2024}
時関系の型の出力値は半角スペースがあるのでevalでうまく行かなそうです。
実際にやってみましょう。
hoge_hash = { key1: Time.current, key2: Date.today }
# => {:key1=>Tue, 11 Jun 2024 09:33:55.541579252 JST +09:00, :key2=>Tue, 11 Jun 2024}
hoge_string = hoge_hash.to_s
# => "{:key1=>Tue, 11 Jun 2024 09:33:55.541579252 JST +09:00, :key2=>Tue, 11 Jun 2024}"
eval(hoge_string)
# (irb):3:in `eval': (eval):1: syntax error, unexpected constant, expecting => (SyntaxError)
# {:key1=>Tue, 11 Jun 2024 09:33:55.541579252 JST ...
やはりエラーが発生しました。
これに対し、今回はこのような時間系のデータがある場合のケースに対応する方法も考えてみたいと思います。
時間系のデータにも対応する
今回は値が確認さえできれば値の方は問わないので時関係の方が文字列で取得できても問題ないこととしました。
def string_to_hash(hash_string)
time_fix = hash_string.gsub(/(.{3}, \d{1,2} Jun \d{4} \d{2}:\d{2}:\d{2}.\d+ JST \+\d{2}:\d{2}|.{3}, \d{1,2} Jun \d{4})/) do |match_string|
"'#{match_string}'"
end
eval(time_fix)
end
いい方法を考え付かなかたので愚直に正規表現を利用し、Time型とDate型の記述を文字列型に置き換えることにしました。
他にいい方法がある方はコメントいただけると幸いです。
evalは実行内容をコントロールできるときに使う
evalをコードで見つけたら無条件に拒絶してしまいがちですがその理由はなぜでしょうか。
私は以下のように思います。
eval実行内容を動的に変化させる時に、悪意のあるプログラムが実行される可能性がある
外部の通信から受け取ったパラメータをevalに食わせることで簡単に脆弱なシステムが出来上がります。パラメータに悪意のあるプログラムを入れられるとそのままevalがそれを実行してしまうからです。
それを回避するために、evalの利用はどのような内容が実行されるかが確定しているtestコードの利用に留めておくのがいいのではないかと私は思いました。