Я пытаюсь разобрать массив разнородных объектов с помощью Codable. Эти объекты также не имеют ключей. Я должен отметить, что у меня правильная структура контейнера, потому что он ДЕЙСТВУЕТ в цикле и всегда печатает «это тип1», как показано ниже. Я просто не могу понять, как получить доступ к фактическому объекту. Вот мой код:

var data: [Any]

public init(from decoder: Decoder) throws {
    var container = try! decoder.container(keyedBy: CodingKeys.self).nestedUnkeyedContainer(forKey: .data)

    while !container.isAtEnd {
        var itemContainer = try container.nestedContainer(keyedBy: CodingKeys.self)

        let itemType = try itemContainer.decode(String.self, forKey: .type)
        switch itemType {
        case "type1":
            print("it is type1")
            // this does not compile, but is what I need
            //let objectOfItem1 = try itemContainer.decode(Type1.self)

            // this compiles, but doesn't work because there is no key with these objects
            //let objectOfItem1 = try itemContainer.decode(Type1, forKey: .type)
        default:
            print("test:: it is the default")
        }
    }
}

private enum CodingKeys: String, CodingKey {
    case data
    case type
}

И вот JSON, который я пытаюсь декодировать (многие свойства указаны для ясности):

"contents" : {
      "data" : [
        {
          "type" : "type1",
          "id" : "6a406cdd7a9cace5"
        }, 
       {
          "type" : "type2",
          "id" : "ljhdgsouilghoipsu"
        }
     ]
 }

Как мне правильно извлечь мои индивидуальные объекты Type1 из этой структуры?

0
jjjjjjjj 21 Ноя 2019 в 17:16
Вероятно, это потому, что вы слишком упрощаете пример, но этот пример выглядит так, как будто его можно смоделировать как struct Item {type: String; id: String}, а затем struct Data {data: [Item]}, а затем вы можете просто декодировать data = try container.decode( Data.self, forKey: .data). Можете ли вы пояснить, что вы подразумеваете под неоднородным и где проявляется эта изменчивость?
 – 
flanker
21 Ноя 2019 в 18:08
Да, структура Item на самом деле уже является другим объектом Codable. Изменчивость возникает в том, что в зависимости от типа элемента (тип1, тип2 и т. Д.) Вместо объекта, являющегося элементом, он может быть другого типа. Таким образом, будет не только struct Item {}, но фактически struct Item2 {} и struct Item3 {} в зависимости от значения type (каждый элемент ДЕЙСТВИТЕЛЬНО имеет свойство 'type').
 – 
joey
21 Ноя 2019 в 18:15
Думал, что может быть так. Одинаковы ли имена ключей в различных «элементах» (например, все id, как в примере) или они тоже различаются?
 – 
flanker
21 Ноя 2019 в 18:25
Объекты представляют собой сложные вложенные структуры, которые имеют общие имена ключей, но не все. Они ни в коем случае не полностью взаимозаменяемы
 – 
joey
21 Ноя 2019 в 18:26
Это беспокоило меня весь день - но я думаю, что теперь у меня это есть. Ответ ниже в ближайшее время ...
 – 
flanker
21 Ноя 2019 в 21:58

2 ответа

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

Чтобы позволить себе это проверить, я немного изменил ваш json, чтобы предоставить мне более разнородные данные для тестирования. Я использовал:

let json = """
{
  "contents": {
    "data": [
      {
        "type": "type1",
        "id": "6a406cdd7a9cace5"
      },
      {
        "type": "type2",
        "dbl": 1.01
      },
      {
        "type": "type3",
        "int": 5
      }
    ]
  }
}

А затем создал три последних типа, представленных этим json

struct Item1: Codable {
   let type: String
   let id: String
}
struct Item2: Codable {
   let type: String
   let dbl: Double
}
struct Item3: Codable {
   let type: String
   let int: Int
}

Чтобы разрешить декодирование нескольких типов безопасным для типов способом (в соответствии с требованиями Codable), вам необходимо использовать один тип, который может представлять (или обертывать) возможные варианты. Для этого хорошо работает перечисление со связанными значениями.

enum Interim {
  case type1 (Item1)
  case type2 (Item2)
  case type3 (Item3)
  case unknown  //to handle unexpected json structures
}

Пока все хорошо, но потом все становится немного сложнее, когда дело доходит до создания Interim из JSON. Ему потребуется перечисление CodingKey, которое представляет все возможные ключи для всех типов Item#, а затем потребуется декодировать JSON, связывая все эти ключи с соответствующими типами и данными:

extension Interim: Decodable {
   private enum InterimKeys: String, CodingKey {
      case type
      case id
      case dbl
      case int
   }
   init(from decoder: Decoder) throws {
      let container = try decoder.container(keyedBy: InterimKeys.self)
      let type = try container.decode(String.self, forKey: .type)
      switch type {
      case "type1":
         let id = try container.decode(String.self, forKey: .id)
         let item = Item1(type: type, id: id)
         self = .type1(item)
      case "type2":
         let dbl = try container.decode(Double.self, forKey: .dbl)
         let item = Item2(type: type, dbl: dbl)
         self = .type2(item)
      case "type3":
         let int = try container.decode(Int.self, forKey: .int)
         let item = Item3(type: type, int: int)
         self = .type3(item)
      default: self = .unknown
      }
   }
}

Это обеспечивает механизм для декодирования разнородных компонентов, теперь нам просто нужно иметь дело с ключами более высокого уровня. Поскольку у нас есть тип Decodable Interim, это просто:

struct DataArray: Decodable {
   var data: [Interim]
}

struct Contents: Decodable {
   var contents: DataArray
}

Теперь это означает, что json можно декодировать следующим образом ...

let data = Data(json.utf8)
let decoder = JSONDecoder()
do {
    let contents = try decoder.decode(Contents.self, from: data)
    print(contents)
} catch {
    print("Failed to decode JSON")
    print(error.localizedDescription)
}

Это успешно декодирует данные во вложенную структуру, где основным компонентом является массив типов Interim с соответствующими объектами Item#. Вышеупомянутое дает следующий вывод, показывающий эти вложенные типы:

Contents(contents: testbed.DataArray(data: [testbed.Interim.type1(testbed.Item1(type: "type1", id: "6a406cdd7a9cace5")), testbed.Interim.type2(testbed.Item2(type: "type2", dbl: 1.01)), testbed.Interim.type3(testbed.Item3(type: "type3", int: 5))]))

Я думаю, что должен быть еще более безопасный способ сделать это с помощью Type Erasure, чтобы предоставить более расширяемое решение, но я еще не в полной мере осмыслил это.

2
flanker 21 Ноя 2019 в 22:17

Я думаю, вам нужно использовать эту структуру:

struct A: Codable {
let contents: B?

enum CodingKeys: String, CodingKey {
    case contents
}

struct B: Codable {
    let data: [C]?

    enum CodingKeys: String, CodingKey {
        case data
    }

    struct C: Codable {
        let type : String?
        let id : String?
    }

}

}

extension A {

init(from decoder: Decoder) throws {

    let container = try decoder.container(keyedBy: CodingKeys.self)

    let contents = try container.decodeIfPresent(B.self, forKey: .contents)

    self.init(contents: contents)
}

}

0
Кирилл Старосельский 21 Ноя 2019 в 18:59