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

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

[
    {
      "data": {
        "children": [
          {
            "name": "Ralph"
          },
          {
            "name": "Woofer"
          }
        ]
      }
    },
    {
      "data": {
        "children": [
          {
            "name": "Spot"
          },
          {
            "name": "Trevor"
          }
        ]
      }
    }
]

Там, где у вас есть эта очень странная структура, где корневым элементом является массив с двумя объектами, и каждый из этих двух объектов является массивом Dog словари.

Но проблема в том, что массив Dog состоит из двух ключей!Вы должны пройти через data и children, чтобы добраться до него.Я видел этот ответ , который изображает выполнение этого с одним глубоким ключом, но я не могу воспроизвести результат, когда он вложен в два глубоких элемента.

Я хочу, чтобы результат был (каккак ни странно) что-то вроде этого, где оба списка поддерживаются отдельно:

struct Result: Codable {
    let dogs1: [Dog]
    let dogs2: [Dog]
}

Я знаю, что мне нужен собственный инициализатор / декодер, но я не совсем уверен, как получить к нему доступ.

Ответы [ 3 ]

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

Используйте промежуточные структуры для погружения в мусорный бак и сбора необходимых данных, а затем утилизируйте их.

Итак, начните со структуры Dog, объявленной на верхнем уровне:

struct Dog : Decodable { let name : String }

В вашем реальном коде создайте временные локальные структуры, чтобы обернуть его и декодировать JSON:

struct TheChildren : Decodable { let children : [Dog] }
struct TheData : Decodable { let data : TheChildren }
let arr = try! JSONDecoder().decode([TheData].self, from: yourJSONdata)

Теперь просто вытащите нужные Dogs:

let dogs = arr.map {$0.data.children}
/*
[[Dog(name: "Ralph"), Dog(name: "Woofer")], 
 [Dog(name: "Spot"), Dog(name: "Trevor")]]
*/

Это массив массивов DogsТаким образом, оба «массива поддерживаются отдельно» в том смысле, что они являются отдельными элементами массива результатов.Это выглядит вполне разумным представлением.

Теперь, если вы хотите добавить эту информацию в новую структуру, хорошо.Она не будет такой же, как ваша положенная структура Result, потому что имена dogs1 и dogs2 нигде не появляются в данных, и вы не можете составить имя свойства во время выполнения (ну, в Swift 4.2 вывроде можно, но это уже другая история).Но дело в том, что у вас есть данные о собаках, легко и без лишних материалов.И нет никакой реальной причины, почему доступ к первому массиву с именем dogs1 лучше, чем получение его по индексу как dogs[0];действительно, последнее на самом деле лучше.Окончание имени свойства с индексным номером - всегда Плохой запах, свидетельствующий о том, что вам действительно нужна коллекция какого-то рода.

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

Вы можете декодировать этот JSON, не вводя промежуточные структуры, сохраняя при этом безопасность типов, декодируя внешний Dictionary, чей единственный ключ data, как вложенный Dictionary типа [String:[String:[Dog]]], что довольно грязно, ноработает, так как у вас есть только 2 вложенных слоя и отдельные ключи во внешних словарях.

struct Dog: Codable {
    let name:String
}

struct Result: Codable {
    let dogs1: [Dog]
    let dogs2: [Dog]

    enum DogJSONErrors: String, Error {
        case invalidNumberOfContainers
        case noChildrenContainer
    }

    init(from decoder: Decoder) throws {
        var containersArray = try decoder.unkeyedContainer()
        guard containersArray.count == 2 else { throw DogJSONErrors.invalidNumberOfContainers}
        let dogsContainer1 = try containersArray.decode([String:[String:[Dog]]].self)
        let dogsContainer2 = try containersArray.decode([String:[String:[Dog]]].self)
        guard let dogs1 = dogsContainer1["data"]?["children"], let dogs2 = dogsContainer2["data"]?["children"] else { throw DogJSONErrors.noChildrenContainer}
        self.dogs1 = dogs1
        self.dogs2 = dogs2
    }
}

Тогда вы можете просто декодировать экземпляр Result следующим образом:

do {
    let dogResults = try JSONDecoder().decode(Result.self, from: dogsJSONString.data(using: .utf8)!)
    print(dogResults.dogs1,dogResults.dogs2)
} catch {
    print(error)
}
0 голосов
/ 30 мая 2018

Итак, краткий ответ: вы не можете, а длинный ответ длиннее.

tl; др

https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types

Один из способов сделать этоэто начиная с промежуточного представления ваших структур.Примерно так:

struct Intermediate: Codable { struct Dog: Codable { let name: String } struct Children: Codable { let children: [Dog] } let data: Children }

и затем вы можете преобразовать это в вашу Result структуру.И вы можете преобразовать вашу Result структуру в промежуточную для сериализации.Это позволяет избежать более сложного использования соответствующих ключей и кодировщиков.Вы можете оставить промежуточные представления закрытыми в своем модуле, если вы не хотите, чтобы кто-то в них копался.

...