如何解决使用PullParser反序列化Crystal中的范围
我正在尝试将一些json { "range": {"start": 1,"stop": 10} }
转换为与Range
等效的Range.new(1,10)
对象。
似乎,如果我想在自己的Foo
结构中执行此操作,则需要一个自定义转换器(请参见下文),该转换器使用JSON::PullParser
来消耗每个令牌。我尝试了以下类似的操作,以了解是否可以理解应该使用提取解析器。但是看起来它希望所有内容都是字符串,并且在找到的第一个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,"stop": 10}})
那么我实际上应该如何在这里使用解析器?
解决方法
您的后一种选择一点也不差。它只是重用了Hash
中的实现,但是它是完全可行且可组合的。唯一的缺点是它需要分配然后丢弃该Hash
。
基于this sample,我推断您应该首先致电.begin_object?
。但这实际上只是一个错误检测的好地方。最主要的是,您还应该显式读取(“消费”)值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,"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,"stop": 10}})
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。