当 Swift 中的 JSON 是一个数组并且第一项与其余项的类型不同时,我该如何解码?

How do I decode JSON in Swift when it's an array and the first item is a different type than the rest?

假设 JSON 看起来像这样:

[
    {
        "name": "Spot",
        "breed": "dalmation"
    },
    {
        "color": "green",
        "eats": "lettuce"
    },
    {
        "color": "brown",
        "eats": "spinach"
    },
    {
        "color": "yellow",
        "eats": "cucumbers"
    }
]

您从 API 返回的 JSON 响应中的第一项始终是狗,之后的所有项始终是乌龟。所以项目 0 是狗,项目 1 到 N-1 是乌龟。

如何将其解析为我可以阅读的内容,例如:

struct Result: Codable {
    let dog: Dog
    let turtles: [Turtle]
}

可能吗?

您可以为您的 Result 结构实现自定义解码器。

init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()

    // Assume the first one is a Dog
    self.dog = try container.decode(Dog.self)

    // Assume the rest are Turtle
    var turtles = [Turtle]()

    while !container.isAtEnd {
        let turtle = try container.decode(Turtle.self)
        turtles.append(turtle)
    }

    self.turtles = turtles
}

只需少量工作,您就可以支持 Dog 词典位于 Turtle 词典数组中的任何位置。

既然你声明你的结构是 Codable 而不仅仅是 Decodable,你也应该从 Encodable 实现自定义 encode(to:) 但这是留给 reader.

的练习

因此您的 Array 包含两种类型的元素。这是 Type1OrType2 问题的一个很好的例子。对于这种情况,您可以考虑使用 enum 和关联类型。对于您的情况,您需要一个 Codable 枚举和 init(from:) throws & func encode(to:) throws

的自定义实现
enum DogOrTurtle: Codable {
    case dog(Dog)
    case turtle(Turtle)

    struct Dog: Codable {
        let name: String
        let breed: String
    }

    struct Turtle: Codable {
        let color: String
        let eats: String
    }
}

extension DogOrTurtle {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            // First try to decode as a Dog, if this fails then try another
            self = try .dog(container.decode(Dog.self))
        } catch {
            do {
                // Try to decode as a Turtle, if this fails too, you have a type mismatch
                self = try .turtle(container.decode(Turtle.self))
            } catch {
                // throw type mismatch error
                throw DecodingError.typeMismatch(DogOrTurtle.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Dog or Turtle)") )
            }
        }
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .dog(let dog):
            try container.encode(dog)
        case .turtle(let turtle):
            try container.encode(turtle)
        }
    }
}

使用这种方法,您无需担心数组中 DogTurtle 的顺序。元素可以以任何顺序和数字出现。

用法:(虽然我故意把狗移到第三个索引处)

let jsonData = """
[
    {
        "color": "green",
        "eats": "lettuce"
    },
    {
        "color": "brown",
        "eats": "spinach"
    },
    {
        "name": "Spot",
        "breed": "dalmation"
    },
    {
        "color": "yellow",
        "eats": "cucumbers"
    }
]
""".data(using: .utf8)!

do {
    let array = try JSONDecoder().decode([DogOrTurtle].self, from: jsonData)
    array.forEach { (dogOrTurtle) in
        switch dogOrTurtle {
        case .dog(let dog):
            print(dog)
        case .turtle(let turtle):
            print(turtle)
        }
    }
} catch {
    print(error)
}