Ios 如何使Swift可编码类型更通用
我目前正在使用一个处理总线预测的API。JSON有一个有趣的怪癖,它是为预测某一站而返回的。当有多个停止预测时,JSON如下所示:Ios 如何使Swift可编码类型更通用,ios,json,swift,codable,Ios,Json,Swift,Codable,我目前正在使用一个处理总线预测的API。JSON有一个有趣的怪癖,它是为预测某一站而返回的。当有多个停止预测时,JSON如下所示: ... "direction": { "prediction": [ { "affectedByLayover": "true", "block": "241", "dirTag": "loo
...
"direction": {
"prediction": [
{
"affectedByLayover": "true",
"block": "241",
"dirTag": "loop",
"epochTime": "1571785998536",
"isDeparture": "false",
"minutes": "20",
"seconds": "1208",
"tripTag": "121",
"vehicle": "1698"
},
{
"affectedByLayover": "true",
"block": "241",
"dirTag": "loop",
"epochTime": "1571787798536",
"isDeparture": "false",
"minutes": "50",
"seconds": "3008",
"tripTag": "122",
"vehicle": "1698"
},
{
"affectedByLayover": "true",
"block": "241",
"dirTag": "loop",
"epochTime": "1571789598536",
"isDeparture": "false",
"minutes": "80",
"seconds": "4808",
"tripTag": "123",
"vehicle": "1698"
}
],
"title": "Loop"
}
...
但是,当只有一个停止预测时,JSON看起来像这样:
...
"direction": {
"prediction":
{
"affectedByLayover": "true",
"block": "241",
"dirTag": "loop",
"epochTime": "1571785998536",
"isDeparture": "false",
"minutes": "20",
"seconds": "1208",
"tripTag": "121",
"vehicle": "1698"
}
"title": "Loop"
}
...
请注意,“预测”不再在数组中——我认为在使用Swift可编码类型解码JSON时,事情会变得越来越复杂。对于“方向”和“预测”,我的模型是这样的
基本上,正在发生的是prediction
中的BTDirection
正在寻找BTPrediction的数组,但是在上面的第二种情况下,这不是数组,因此解码失败。如何使模型更灵活以适应阵列或单个对象?理想情况下,在第二种情况下,prediction
仍然是单个BTDirection
的数组。在此方面的任何帮助都将不胜感激。您可以试试
struct BTDirection:Codable {
let title,stopTitle: String
let prediction: [BTPrediction]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
stopTitle = try container.decode(String.self, forKey: .stopTitle)
do {
let res = try container.decode([BTPrediction].self, forKey: .prediction)
prediction = res
}
catch {
let res = try container.decode(BTPrediction.self, forKey: .prediction)
prediction = [res]
}
}
}
为了补充Sh_Khan的答案,如果您的API响应中有多个地方发生了这种情况,您可以将这种自定义解码和编码提取到自定义包装器类型,这样您就不必到处重复,如:
///可以编码/解码到或从中编码/解码的包装类型
///一个'Element'数组或一个'Element'。
结构ArrayOrSingleItem{
私有变量元素:[元素]
}
扩展阵列单项:可解码,其中元素:可解码{
init(来自解码器:解码器)抛出{
let container=尝试解码器。singleValueContainer()
做{
//首先尝试将单个值解码为'Element'数组。
elements=try container.decode([Element].self)
}抓住{
//如果解码为'Element'数组不起作用,请尝试解码
//将单个值作为单个“元素”,并将其存储在数组中。
elements=try[container.decode(Element.self)]
}
}
}
扩展ArrayOrSingleItem:可编码,其中元素:可编码{
func encode(到编码器:编码器)抛出{
var container=encoder.singleValueContainer()
如果elements.count==1,则让element=elements.first{
//如果'Element'的包装数组正好有一个'Element'
//在其中,将其编码为一个“元素”。
尝试container.encode(元素)
}否则{
//否则,将包装好的数组按原样编码-数组
//属于'Element's。
尝试container.encode(元素)
}
}
}
//这使您可以将“ArrayOrSingleItem”视为元素的集合。
//如果需要类型为“Array”的元素,只需实例化一个新的
//您的'ArrayOrSingleItem'中的'Array',如:
//let方向:ArrayOrSingleItem=。。。
//let数组:[BTDirection]=数组(方向)
扩展ArrayOrSingleItem:MutableCollection{
下标(位置:Int)->元素{
获取{elements[position]}
集合{elements[position]=newValue}
}
var startIndex:Int{elements.startIndex}
var endIndex:Int{elements.endIndex}
func索引(在i:Int之后)->Int{
元素索引(在:i之后)
}
}
//这允许您从“Array”文本实例化“ArrayOrSingleItem”。
扩展数组单项:ExpressibleByArrayLiteral{
init(阵列并行元素:元素…){
self.elements=元素
}
}
然后,您可以像下面这样声明您的预测(以及可能成为API响应中的数组或单个项的任何其他属性):
...
"direction": {
"prediction": [
{
"affectedByLayover": "true",
"block": "241",
"dirTag": "loop",
"epochTime": "1571785998536",
"isDeparture": "false",
"minutes": "20",
"seconds": "1208",
"tripTag": "121",
"vehicle": "1698"
},
{
"affectedByLayover": "true",
"block": "241",
"dirTag": "loop",
"epochTime": "1571787798536",
"isDeparture": "false",
"minutes": "50",
"seconds": "3008",
"tripTag": "122",
"vehicle": "1698"
},
{
"affectedByLayover": "true",
"block": "241",
"dirTag": "loop",
"epochTime": "1571789598536",
"isDeparture": "false",
"minutes": "80",
"seconds": "4808",
"tripTag": "123",
"vehicle": "1698"
}
],
"title": "Loop"
}
...
struct BTDirection:Codable{
标题:字符串?
让我们停止标题:字符串?
让预测:ArrayOrSingleItem?
}
无论是@TylerTheCompiler还是@Sh_Khan,都为解决方案提供了非常好的技术输入,提供了解决方案的机制,但是提供的代码会在给定的json数据中遇到一些实现问题:
发布的JSON中有一些错误将停止codable使用它-我怀疑这些只是复制和粘贴错误,但如果不是,您将遇到前进的问题
由于初始的方向
键,JSON实际上有3层(或至少2.5层)嵌套。这要么需要在init(from:)
中展平,要么需要一个临时结构以便于映射。在初始化器中展平会更加优雅,临时结构会更快:-)
编码键虽然很明显,但在前面的回答中没有定义,因此会导致编译init时出错(从:)
JSON中没有stopTitle
字段,因此解码时会出错,除非将其视为可选字段。在这里,我将其视为一个具体的字符串
,并在解码过程中进行处理;你可以把它变成一个字符串?
,然后解码器就会处理它不存在的问题
使用“更正的”JSON(添加了大括号、缺少逗号等),下面的代码将导入这两种场景。我还没有实现arrayOrSingleItem
,因为这一切都应该归功于@TylerTheCompiler,但您可以很容易地把它放进去
struct Direction: Decodable {
let direction: BTDirection
}
struct BTDirection: Decodable {
enum CodingKeys: String, CodingKey {
case title
case stopTitle
case prediction
}
let prediction: [BTPrediction]
let title: String
let stopTitle: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
prediction = try container.decode([BTPrediction].self, forKey: .prediction)
} catch {
let singlePrediction = try container.decode(BTPrediction.self, forKey: .prediction)
prediction = [singlePrediction]
}
title = try container.decode(String.self, forKey: .title)
stopTitle = try container.decodeIfPresent(String.self, forKey: .stopTitle) ?? "unnamed stop"
}
}
struct BTPrediction: Decodable {
let minutes: String
let vehicle: String
}
然后实际解码JSON,解码顶级方向类型
let data = json.data(using: .utf8)
if let data = data {
do {
let bus = try decoder.decode(Direction.self, from: data)
// extract the BTDirection type from the temporary Direction type
// and do something with the decoded data
}catch {
//handle error
}
}
如果您不知道,它对于验证/更正json非常有用