4

假设 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]
}

可能吗?

4

2 回答 2

11

您可以为您的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

由于您声明您的结构是可编码的,而不仅仅是可解码的,您还应该实现encode(to:)来自 Encodable 的自定义,但这是留给读者的练习。

于 2018-05-30T02:15:42.387 回答
1

所以你Array包含两种类型的元素。这是一个很好的Type1OrType2问题示例。对于这种类型的情况,可以考虑使用enumwith 关联类型。对于您的情况,您需要一个具有&Codable自定义实现的枚举init(from:) throwsfunc 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)
        }
    }
}

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

用法:(虽然我故意在第三个索引处移动了狗)

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)
}
于 2018-05-30T06:34:59.097 回答