iTranslated by AI
Hashes that get JSON.parsed instead of JSON.generated
A story about Ruby.
In Ruby, there is a method called JSON(object) that returns a Hash if you pass it a string, and converts it to JSON if you pass it a Hash, as shown below.
h = {a: 1}
JSON(h) #=> "{\"a\": 1}"
j = "{\"a\": 1}"
JSON(j) #=> {a: 1}
However, this doesn't work for certain "ill-behaved" Hashes extended via method_missing. For example, this is the case with Rails encrypted credentials in Rails 7.
h = Rails.credentials.hoge
h.to_a? Hash #=> true
JSON(h) #=> TypeError: no implicit conversion of nil into String
Looking at the implementation of JSON(object), it is as follows. If object.respond_to? :to_str is true, it is treated as a string and JSON.parse is executed; otherwise, JSON.generate is executed.
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
In the previous example, h.respond_to? :to_str becomes true. This is because Rails credentials are set up to return something for all calls (including to_str) via method_missing. Therefore, it behaves as follows:
h = Rails.credentials.hoge
h.respond_to? :to_str #=> true
h.to_str #=> nil
As a result, within JSON(h), it is treated as a string-like object and JSON.parse is executed, resulting in an error.
While I feel this might be more of an issue with ActiveSupport than the JSON implementation, I thought it would be more rigorous to check not just respond_to? :to_str but also whether the result of to_str is actually a String to verify it is a string-like object, so I submitted a PR. Well, it's perfectly fine if it's not merged. After all, using an explicit JSON.generate would solve the problem anyway...
Discussion