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?