The Crystal Programming Language Forum

Allow EpochConverter to receive null json value

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

     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)

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:

While on the other-hand, using JSON.mapping, you run into this problem:

Thank you @Blacksmoke16. I will convert it to JSON::Serializable and try with that.

The following should work:

require "json"

class MyObject
    created_at: {type: Time?, converter: Time::EpochConverter, nilable: true}

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
    created_at: {type: Time?, converter: NilableConverter(Time::EpochConverter)}

module NilableConverter(T)
  def self.from_json(value : JSON::PullParser)
    value.read_null_or do

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!