Crystal: JSON#mapping with custom converters and array types

Created on 21 Jul 2019  路  7Comments  路  Source: crystal-lang/crystal

When trying to parse JSON with an array of time objects, and using a custom converter, I'm getting an error that #to_json doesn't output an array. I would have expected the code generated from JSON#mapping macro to know to apply the converter to each individual element in the array, but it seems otherwise. I'm not sure, is this the intended functionality, or is it a bug?

Sample code:

require "json"

class MyClass
  JSON.mapping(
    times: {type: Array(Time), converter: Time::EpochConverter}
  )
end

times = [] of Int64

times << Time.new.to_unix
times << Time.utc.to_unix

times.to_json

json = MyClass.from_json(times.to_json)
json.to_json

The specific error is:

no overload matches 'Time::EpochConverter.to_json' with types Array(Time), JSON::Builder
Overloads are:

  • Time::EpochConverter.to_json(value : Time, json : JSON::Builder)
  • Object#to_json(io : IO)
  • Object#to_json()
feature topicserialization

Most helpful comment

Of course!

All 7 comments

Time::EpochConverter is for converting a String into Time and the other way around, not from Array into Time.

This is an interesting problem.

I think one way we can achieve this is by having JSON::ArrayConverter(Converter) which basically encodes/decodes an array using another converter as a generic type argument. Then you would use it like this:

require "json"

class MyClass
  JSON.mapping(
    times: {type: Array(Time), converter: JSON::ArrayConverter(Time::EpochConverter)}
  )
end

times = [] of Int64

times << Time.new.to_unix
times << Time.utc.to_unix

times.to_json

json = MyClass.from_json(times.to_json)
json.to_json

We should probably have JSON::HashConverter(Converter) too. And do the same with YAML.

What do you think?

I did a small test and it seems to work fine. The implementation I have so far is:

module JSON::ArrayConverter(Converter)
  def self.from_json(pull : JSON::PullParser)
    ary = Array(typeof(Converter.from_json(pull))).new
    pull.read_array do
      ary << Converter.from_json(pull)
    end
    ary
  end

  def self.to_json(values : Array, builder : JSON::Builder)
    builder.array do
      values.each do |value|
        Converter.to_json(value, builder)
      end
    end
  end
end

I think it's a great solution, along with the HashConverter and adding both to YAML#mapping as well.

I just used your implementation in the project where I discovered this issue, and it appears to be working fine. Is it ok if I continue to use it until this gets added to the core library?

Of course!

@asterite do you mind PRing this?

It's a bit of work so I'll take my time, unless someone wants to work on this.

@asterite Have you started or would be ok if try to work on this?

@rodrigopinto Nope, please go ahead if you want 馃檱

Was this page helpful?
0 / 5 - 0 ratings