使用 PullParser 反序列化 Crystal 中的范围
Use PullParser to deserialize Range in Crystal
我正在尝试将一些 json { "range": {"start": 1, "stop": 10} }
转换为等同于 Range.new(1,10)
.
的 Range
对象
看来,如果我想在我的 Foo
结构中执行此操作,我将需要一个使用 JSON::PullParser
来使用每个标记的自定义转换器(见下文)。我尝试了类似下面的事情,看看我是否能理解应该如何使用 pull 解析器。但看起来它希望一切都是字符串,并在它找到的第一个 Int 上阻塞。所以以下内容没有帮助,但说明了我的困惑:
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}})
我能够解决这个问题的唯一方法是只读取原始 json 字符串并直接使用它,但感觉就像我在绕过解析器,因为我不明白它。以下作品:
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}})
那么我应该如何在这里使用解析器?
你的后一种选择一点也不差。它只是重用了 Hash
的实现,但它是完全可行和可组合的。唯一的缺点是它需要分配然后丢弃 Hash
.
基于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。在下面的代码中,它用 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}})
Oleh Prypin 的回答很棒。正如他所说,第二种方法很好,只是它分配了一个哈希,所以它会消耗额外的内存。
您可以使用在堆栈上分配的 NamedTuple 而不是 Hash,这样效率更高。这是此类类型的一个很好的用例:
require "json"
module RangeConverter
def self.from_json(pull : JSON::PullParser)
tuple = NamedTuple(start: Int32, stop: Int32).new(pull)
tuple[:start]..tuple[: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}})
NamedTuple
的替代方法是使用带有 getter 的普通结构,这就是 record
的用途:
require "json"
record JSONRange, start : Int32, stop : Int32 do
include JSON::Serializable
def to_range
start..stop
end
end
module RangeConverter
def self.from_json(pull : JSON::PullParser)
JSONRange.new(pull).to_range
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}})
我正在尝试将一些 json { "range": {"start": 1, "stop": 10} }
转换为等同于 Range.new(1,10)
.
Range
对象
看来,如果我想在我的 Foo
结构中执行此操作,我将需要一个使用 JSON::PullParser
来使用每个标记的自定义转换器(见下文)。我尝试了类似下面的事情,看看我是否能理解应该如何使用 pull 解析器。但看起来它希望一切都是字符串,并在它找到的第一个 Int 上阻塞。所以以下内容没有帮助,但说明了我的困惑:
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}})
我能够解决这个问题的唯一方法是只读取原始 json 字符串并直接使用它,但感觉就像我在绕过解析器,因为我不明白它。以下作品:
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}})
那么我应该如何在这里使用解析器?
你的后一种选择一点也不差。它只是重用了 Hash
的实现,但它是完全可行和可组合的。唯一的缺点是它需要分配然后丢弃 Hash
.
基于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。在下面的代码中,它用 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}})
Oleh Prypin 的回答很棒。正如他所说,第二种方法很好,只是它分配了一个哈希,所以它会消耗额外的内存。
您可以使用在堆栈上分配的 NamedTuple 而不是 Hash,这样效率更高。这是此类类型的一个很好的用例:
require "json"
module RangeConverter
def self.from_json(pull : JSON::PullParser)
tuple = NamedTuple(start: Int32, stop: Int32).new(pull)
tuple[:start]..tuple[: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}})
NamedTuple
的替代方法是使用带有 getter 的普通结构,这就是 record
的用途:
require "json"
record JSONRange, start : Int32, stop : Int32 do
include JSON::Serializable
def to_range
start..stop
end
end
module RangeConverter
def self.from_json(pull : JSON::PullParser)
JSONRange.new(pull).to_range
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}})