✨
JSON.generateしてほしいのにJSON.parseされてしまうHash
Rubyの話。
Rubyでは下記のように書くと文字列ならHashを返し、Hashを渡すとJSONにして返してくれるJSON(object)
というメソッドがある。
h = {a: 1}
JSON(h) #=> "{\"a\": 1}"
j = "{\"a\": 1}"
JSON(j) #=> {a: 1}
しかしこれは一部の行儀の悪いmethod_missingで拡張されたHashに対しては機能しない。例えばRails7のRails encrypted credentialsの場合がそれである。
h = Rails.credentials.hoge
h.to_a? Hash #=> true
JSON(h) #=> TypeError: no implicit conversion of nil into String
JSON(h)
の実装を読んでみると下記のようになっている。objectのrespond_to? :to_str
がtrueならそれを文字列と見なしJSON.parse
、そうでないならJSON.generate
を実行している。
def JSON(object, *args)
if object.respond_to? :to_str
JSON.parse(object.to_str, args.first)
else
JSON.generate(object, args.first)
end
end
先の例だとh.respond_to? :to_str
はtrueになる。これはRails credentialsがmethod_missingで全ての呼び出し(to_strを含む)に対して何かしらを返すようになっているからである。だから下記のようになる
h = Rails.credentials.hoge
h.respond_to? :to_str #=> true
h.to_str #=> nil
結果的にJSON(h)
内部ではStringライクなオブジェクトと見なされてJSON.parse
されてしまいエラーが発生する。
これはJSON側の実装というよりもActiveSupportの方の問題という気もするが、一応Stringライクなオブジェクトであるというチェックにrespond_to? :to_str
だけでなく実際にto_str
の結果がStringであるかのチェックも入れたほうが厳密な気がするのでPRを出しておいた。まぁ取り込まれなくても全然問題ないが。そもそも明示的JSON.generate
を使えば済む話ではあるので。。
Discussion