Swift Codable: Continue parsing object after nested object error

时间:2018-09-18 20:08:22

标签: swift swift4 codable

My app is, like oh so many apps, retrieving JSON from an API and converting it using the new Codable protocol in Swift 4. Most of the time, this works fine and as expected. However, sometimes the API will send me unexpected garbage. Incorrect types, arrays with just null inside, that kind of thing.

The problem is that the objects involved can be large and complicated, and when I'm parsing a child object and it fails, the whole object fails, all the way up to the root. I'm including a very simple playground example to illustrate the concept; the actual objects involved are way more complex.

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)")
}

Output:

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 is expected to be an array of objects, but in the badJSON case, it is a single object, which causes the .typeMismatch exception and kills the parsing right there.

What I'm looking for is a way to allow errors like this one to kill the parsing for the child object only and allow parsing of the parent object to continue. I'm looking to do this in a generic fashion, so I don't have to special case each and every object in my app to specifically handle whatever bad data the API delivers. I'm not sure if there even is a solution for this, I haven't had any luck finding anything, but it would sure improve my quality of life if there is. Thanks!

1 个答案:

答案 0 :(得分:1)

您可以尝试按照注释中的建议自定义init(来自解码器:Decoder),

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)
    }
}
相关问题