JSON::Serializable, converters, and nilable values

In talking to a third-party API, it’s sending Time values serialized as integers, so I’m using Time::EpochConverter to deserialize it. , but when that timestamp is nilable and is explicitly returned from the API as null, I can’t use it because I get this:

Expected Int but was Null at line 1, column 222

Is there a good pattern for dealing with explicit null values when using a converter?

Just make your property nilable?

require "json"

class Person
  include JSON::Serializable

  @[JSON::Field(converter: Time::EpochConverter)]
  property birth_date : Time?
end

person = Person.from_json(%({"birth_date": null}))
person.birth_date # => nil

This used to work up until 1.8.2 but it’s broken in 1.9.0. This looks like a regression introduced in one of the JSON::Serializable refactors (probably Simplify implementation of `Serializable#initialize` by straight-shoota · Pull Request #13433 · crystal-lang/crystal · GitHub).

As a workaround, you can make the converter nilable:

require "json"

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

class Person
  include JSON::Serializable

  @[JSON::Field(converter: NilableConverter(Time::EpochConverter))]
  property birth_date : Time?
end

person = Person.from_json(%({"birth_date": null}))
p person.birth_date # => nil
1 Like
4 Likes

Ahh that would explain it :person_facepalming: