我的应用程序就像很多应用程序一样,从 API 中检索 JSON 并使用 Swift 4 中的新Codable
协议进行转换。大多数情况下,这工作正常且符合预期。但是,有时 API 会给我发送意想不到的垃圾。不正确的类型,内部只有null
的数组,诸如此类。
问题是所涉及的对象可能又大又复杂,当我解析子对象并且它失败时,整个对象都会失败,一直到根。我包括一个非常简单的游乐场示例来说明这个概念;所涉及的实际对象要复杂得多。
let goodJSON = """
{
"name": "Fiona Glenanne",
"vehicles": [
{
"make": "Saab",
"model": "9-3",
"color": "Black"
},
{
"make": "Hyundai",
"model": "Genesis",
"color": "Blue"
}
]
}
"""
let goodJSONData = goodJSON.data(using: .utf8)!
let badJSON = """
{
"name": "Michael Westen",
"vehicles": {
"make": "Dodge",
"model": "Charger",
"color": "Black"
}
}
"""
let badJSONData = badJSON.data(using: .utf8)!
struct Character: Codable {
let name: String
let vehicles: [Vehicle]
}
struct Vehicle: Codable {
let make: String
let model: String
let color: String
}
do {
let goodCharacter = try JSONDecoder().decode(Character.self, from: goodJSONData)
print(goodCharacter)
} catch {
print(error)
}
do {
let badCharacter = try JSONDecoder().decode(Character.self, from: badJSONData)
print(badCharacter)
} catch DecodingError.typeMismatch(let type, let context) {
print("Got (type); (context.debugDescription) ** Path:(context.codingPath)")
} catch {
print("Caught a different error: (error)")
}
输出:
Character(name: "Fiona Glenanne", vehicles: [__lldb_expr_20.Vehicle(make: "Saab", model: "9-3", color: "Black"), __lldb_expr_20.Vehicle(make: "Hyundai", model: "Genesis", color: "Blue")])
Got Array<Any>; Expected to decode Array<Any> but found a dictionary instead. ** Path:[CodingKeys(stringValue: "vehicles", intValue: nil)]
vehicles
应该是对象数组,但在badJSON
情况下,它是一个对象,这会导致.typeMismatch
异常并在那里终止解析。
我正在寻找一种允许此类错误仅终止子对象的解析并允许继续解析父对象的方法。我希望以通用方式执行此操作,因此我不必对应用程序中的每个对象进行特殊处理,以专门处理 API 提供的任何错误数据。我不确定是否有解决方案,我没有找到任何东西,但如果有的话,它肯定会提高我的生活质量。谢谢!
您可以尝试按照注释中的建议自定义 init(来自解码器:解码器(,这将是这样的,
struct Character: Codable {
let name: String
let vehicles: [Vehicle]
private enum CodingKeys: String, CodingKey { case name, vehicles }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
do {
let vehicle = try container.decode(Vehicle.self, forKey: .vehicles)
vehicles = [vehicle]
} catch DecodingError.typeMismatch {
vehicles = try container.decode([Vehicle].self, forKey: .vehicles)
}
}