I'm trying to convert some json { "range": {"start": 1, "stop": 10} }
into a Range
object equivalent to Range.new(1,10)
.
It seems that if I want to do this in my Foo
struct I'll need a custom converter (see below) which uses the JSON::PullParser
to consume each token. I tried things like the below to see if I could understand how the pull parser is supposed to be used. But it looks like it expects everything to be a string and chokes on the first Int it finds. So the following isn't helpful but illustrates what I'm confused about:
require "json"
module RangeConverter
def self.from_json(pull : JSON::PullParser)
pull.read_object do |key, key_location|
puts key # => puts `start` then chokes on the `int`
# Expected String but was Int at 1:22
end
Range.new(1,2)
end
end
struct Foo
include JSON::Serializable
@[JSON::Field(converter: RangeConverter)]
property range : Range(Int32, Int32)
end
Foo.from_json %({"range": {"start": 1, "stop": 10}})
The only way I was able to figure this out was to just read the raw json string and work with it directly but it feels like I'm side-stepping the parser because I don't understand it. The following works:
require "json"
module RangeConverter
def self.from_json(pull : JSON::PullParser)
h = Hash(String, Int32).from_json(pull.read_raw)
Range.new(h["start"],h["stop"])
end
end
struct Foo
include JSON::Serializable
@[JSON::Field(converter: RangeConverter)]
property range : Range(Int32, Int32)
end
Foo.from_json %({"range": {"start": 1, "stop": 10}})
So how am I actually supposed to be using the Parser here?
Your latter option isn't bad at all. It just reuses the implementation from a Hash
but it's fully workable and composable. The only downside is it needs to allocate and then discard that Hash
.
Based on this sample I deduce that you're expected to call .begin_object?
first. But actually that's just a nicety for error detection. The main thing is that you're also supposed to explicitly read ("consume") the values, based on this sample. In the code below this is represented with Int32.new(pull)
.
require "json"
module RangeConverter
def self.from_json(pull : JSON::PullParser)
start = stop = nil
unless pull.kind.begin_object?
raise JSON::ParseException.new("Unexpected pull kind: #{pull.kind}", *pull.location)
end
pull.read_object do |key, key_location|
case key
when "start"
start = Int32.new(pull)
when "stop"
stop = Int32.new(pull)
else
raise JSON::ParseException.new("Unexpected key: #{key}", *key_location)
end
end
raise JSON::ParseException.new("No start", *pull.location) unless start
raise JSON::ParseException.new("No stop", *pull.location) unless stop
Range.new(start, stop)
end
end
struct Foo
include JSON::Serializable
@[JSON::Field(converter: RangeConverter)]
property range : Range(Int32, Int32)
end
p Foo.from_json %({"range": {"start": 1, "stop": 10}})