Как мне декодировать JSON в Swift, когда это массив, а первый элемент отличается от остальных? - PullRequest
0 голосов
/ 30 мая 2018

Скажем, JSON выглядит следующим образом:

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

Где первый элемент в ответах JSON, который вы получаете от API, всегда является собакой, а все последующие - черепахами.Таким образом, пункт 0 - это собака, пункты с 1 по N-1 - черепахи.

Как мне разобрать это в то, что я могу прочитать, например:

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

Возможно ли это?

Ответы [ 2 ]

0 голосов
/ 30 мая 2018

Итак, ваш 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)
        }
    }
}

. При таком подходе вам не нужно беспокоиться о порядке Dogили 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)
}
0 голосов
/ 30 мая 2018

Вы можете реализовать собственный декодер для вашей 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, вам также следует реализовать пользовательский encode(to:) из Encodable, но это упражнение оставлено читателю.

...