Как мне декодировать JSON в Swift, где это массив с двойными вложенными элементами?

Скажем, 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]
}

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

Итак, у вас, по сути, есть тип Dog, который имеет свойство name, и вы хотите декодировать один массив [Dog] из ответа JSON на свой вопрос?

Dávid Pásztor 30.05.2018 17:03

@ DávidPásztor Извините, что неясно. Я обновлю вопрос

Doug Smith 30.05.2018 17:05

Иногда проще использовать JSONSerialization. Это работало годами, прежде чем появился Codable.

rmaddy 30.05.2018 17:07

@rmaddy Я знаю, но я пытаюсь понять этот Swift

Doug Smith 30.05.2018 17:09

@matt Что ты имеешь в виду? Это очень похоже на проприетарный JSON от компании, в которой я работаю, которую я не могу изменить. Если у вас другое представление данных в Swift, я все слышу

Doug Smith 30.05.2018 17:24
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
1
5
941
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

tl; dr

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 в промежуточную для сериализации. Это позволяет избежать более сложного использования ключей и кодировщиков. Вы можете оставить промежуточные представления приватными в своем модуле, если не хотите, чтобы кто-то в них тыкался.

Подождите, так что длинный ответ - что вы можете, правда? Это немного утомительно?

Doug Smith 30.05.2018 17:20

Да, это может быть сделано. Да, это скучно. и да, вероятно, лучше перекодировать ваш json, чем ваш swift, если это имеет смысл.

Joshua Smith 30.05.2018 17:23

Хотелось бы, чтобы это действительно очень странный JSON, но я не контролирую его

Doug Smith 30.05.2018 17:28

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

Итак, начнем со структуры 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)

Теперь просто вытащите желаемых Собак:

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]; действительно, последнее на самом деле лучше. Окончание имени свойства индексным номером - всегда. Плохой запах, предполагающий, что вам действительно нужна какая-то коллекция.

Ответ принят как подходящий

Вы можете декодировать этот 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)
}

Кажется, не работает Я получаю keyNotFound(CodingKeys(stringValue: "author", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), _DictionaryCodingKey(stringValue: "data", intValue: nil), _DictionaryCodingKey(stringValue: "children", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"author\", intValue: nil) (\"author\").", underlyingError: nil)) с распечатанного error.

Doug Smith 30.05.2018 18:00

@DougSmith Я просто скопировал вставленный JSON из вашего вопроса, и он работает нормально. Это сообщение об ошибке особенно странно, поскольку в структурах или JSON не должно быть ключа author. Вы уверены, что точно скопировали этот код и использовали JSON из вопроса?

Dávid Pásztor 30.05.2018 18:12

Другие вопросы по теме