First, sorry that I am still on Crystal v0.30, and still using JSON.mapping. But likely the issue I am having does not have to do with this.
I have field called timecreated
in the JSON that I send to server. The JSON.mapping code is
JSON.mapping(
timecreated: {type: Time?, converter: Time::EpochConverter}
)
If I pass a JSON value in my postdata where timecreated field is null
then I get an error saying Expected Int but was Null
. This is despite declaring the field as nilable
. I thought, this perhaps has to do with Time::EpochConverter
not being able to handle null
JSON value. (If I pass an integer for timecreated, I don’t get any error). So, I tried to extend Time::EpochConverter
to allow for null
values by adding the following code to my src
file.
module Time::EpochConverter
def self.from_json(value : JSON::PullParser) : Time|Nil
if (value.read_int_or_null)
Time.unix(value.read_int)
else
nil
end
end
end
With this code added, null
no longer throws an error. However, if I pass an integer
JSON value to the timecreated
field, I get the following error:
Expected Int but was String
I have two questions: (1) Why is it throwing this Expected Int but was String
error because of the code I added?
(2) How do I allow EpochConverter to accept null
json values.
Thank you.
Given JSON.mapping
is deprecated, I would just change your implementation to use JSON::Serializable
, which doesn’t seem to have this problem: https://play.crystal-lang.org/#/r/9bb0.
While on the other-hand, using JSON.mapping
, you run into this problem: https://play.crystal-lang.org/#/r/9bb2.
Thank you @Blacksmoke16. I will convert it to JSON::Serializable and try with that.
The following should work:
require "json"
class MyObject
JSON.mapping(
created_at: {type: Time?, converter: Time::EpochConverter, nilable: true}
)
end
pp MyObject.from_json %({"created_at":null})
pp MyObject.from_json %({"created_at":1592929230})
It seems that the logic to wrap the pull_parser with a read_null_or
is not handled when the type is nilable but there is no nilable explicit named argument.
And just because it’s fun, this would be another alternative
class MyObject
JSON.mapping(
created_at: {type: Time?, converter: NilableConverter(Time::EpochConverter)}
)
end
module NilableConverter(T)
def self.from_json(value : JSON::PullParser)
value.read_null_or do
T.from_json(value)
end
end
end
Thank you @bcardiff. I will try this. I tried using JSON::Serializable. Specifically, using
@[JSON::Field(emit_null: true, converter: Time::EpochConverter)]
property timecreated : Time?
But with that I get the error:
Expected String but was Int
I don’t know why it is expecting a String. Could be because I am using an older version of Crystal. But let me try your solution. Thank you.
The above solution worked great. Thank you!