It’s RFC week! ![]()
If you write a code like this:
puts [1, 2, 3].to_json
You get this error:
Error: undefined method 'to_json' for Array(Int32)
Huh? But Object has a to_json method, I see it in the docs!
Well, the problem is that we define that method for all objects when you require "json", and given that the docs include all docs of the standard library, it’s not clear that this only works if you require "json".
So the first thing we should do is to actually document that this will only work if you require "json".
Another thing we could do is to define a to_json method on Object even when “json” is not required. Like this:
class Object
def to_json
{%
raise <<-MSG
undefined method `to_json` for #{@type}
Did you maybe forget to `require "json"`?
MSG
%}
end
end
Then with the original [1, 2, 3].to_json code the error you get is this now:
Error: undefined method `to_json` for Array(Int32)
Did you maybe forget to `require "json"`?
You can see that running here: Carcin
The only downside of this is that if for some reason you use another JSON library, not the standard one, that error might be confusing. But… I don’t think there’s another JSON library. The one in the standard library is pretty good! And we should also aim for the 99% of users who are going to use JSON.
This approach might sound a bit hacky or unsound, but I’m sure it will help people a lot. Pragmatism is what Crystal is all about.
A second thing we can do here is that once you do require "json", if you try to convert any object to JSON it won’t work:
require "json"
class Foo
end
Foo.new.to_json
The error is:
Error: no overload matches 'Foo#to_json' with type JSON::Builder
Overloads are:
- Object#to_json(io : IO)
- Object#to_json()
What? I did require "json"! And I can see there are some overloads… so what’s going on?
So here we could define this:
class Object
def to_json(builder : JSON::Builder)
{%
raise <<-MSG
no overload matches '#{@type}#to_json' with type JSON::Builder
Maybe #{@type} doesn't `include JSON::Serializable`?
MSG
%}
end
end
Then the error you get is this:
Error: no overload matches 'Foo#to_json' with type JSON::Builder
Maybe Foo doesn't `include JSON::Serializable`?
I think that’s a bit better.
An alternative to this is to not define to_json and to_json(IO) on every object, only on the ones that actually define to_json(JSON::Builder). I don’t know.
Then we could do something similar with YAML.
What do you think?