Используйте swift Codable для декодирования JSON со значениями в качестве ключей - PullRequest
0 голосов
/ 10 января 2019

У меня проблема с декодированием структуры JSON, которую я не могу изменить, чтобы упростить декодирование (это происходит из firebase) ..

Как мне декодировать следующий JSON в объекты? Проблема в том, как конвертировать "7E7-M001". Это название контейнера с выдвижными ящиками. Имя ящика также используется в качестве ключа.

{
  "7E7-M001" : {
    "Drawer1" : {
      "101" : {
        "Partnumber" : "F101"
      },
      "102" : {
        "Partnumber" : "F121"
      }
    }
  },
  "7E7-M002": {
    "Drawer1": {
      "201": {
        "Partnumber": "F201"
      },
      "202": {
        "Partnumber": "F221"
      }
    }
  }
}

Что я должен исправить в классе Container & Drawer, чтобы ключ имел свойство title и массив объектов в этих классах?

class Container: Codable {
    var title: String
    var drawers: [Drawer]
}

class Drawer: Codable {
    var title: String
    var tools: [Tool]
}

class Tool: Codable {
    var title: String
    var partNumber: String

    enum CodingKeys: String, CodingKey {
        case partNumber = "Partnumber"
    }
}

Ответы [ 2 ]

0 голосов
/ 10 января 2019

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

Центральным инструментом для обработки ключей с неизвестным значением является CodingKey, который может обрабатывать любую строку:

struct TitleKey: CodingKey {
    let stringValue: String
    init?(stringValue: String) { self.stringValue = stringValue }
    var intValue: Int? { return nil }
    init?(intValue: Int) { return nil }
}

Вторым важным инструментом является способность знать свой собственный заголовок. Это значит спрашивать декодер "где мы?" Это последний элемент в текущем пути кодирования.

extension Decoder {
    func currentTitle() throws -> String {
        guard let titleKey = codingPath.last as? TitleKey else {
            throw DecodingError.dataCorrupted(.init(codingPath: codingPath,
                                                    debugDescription: "Not in titled container"))
        }
        return titleKey.stringValue
    }
}

А затем нам нужен способ декодирования элементов, которые «озаглавлены» следующим образом:

extension Decoder {
    func decodeTitledElements<Element: Decodable>(_ type: Element.Type) throws -> [Element] {
        let titles = try container(keyedBy: TitleKey.self)
        return try titles.allKeys.map { title in
            return try titles.decode(Element.self, forKey: title)
        }
    }
}

С этим мы можем придумать протокол для этих «титулованных» вещей и расшифровать их:

protocol TitleDecodable: Decodable {
    associatedtype Element: Decodable
    init(title: String, elements: [Element])
}

extension TitleDecodable {
    init(from decoder: Decoder) throws {
        self.init(title: try decoder.currentTitle(),
                  elements: try decoder.decodeTitledElements(Element.self))
    }
}

И это большая часть работы. Мы можем использовать этот протокол, чтобы сделать декодирование довольно простым для уровней верхнего уровня. Просто внедрите init(title:elements:).

struct Drawer: TitleDecodable {
    let title: String
    let tools: [Tool]
    init(title: String, elements: [Tool]) {
        self.title = title
        self.tools = elements
    }
}

struct Container: TitleDecodable {
    let title: String
    let drawers: [Drawer]

    init(title: String, elements: [Drawer]) {
        self.title = title
        self.drawers = elements
    }
}

Tool немного отличается, так как это листовой узел и есть другие вещи для декодирования.

struct Tool: Decodable {
    let title: String
    let partNumber: String

    enum CodingKeys: String, CodingKey {
        case partNumber = "Partnumber"
    }

    init(from decoder: Decoder) throws {
        self.title = try decoder.currentTitle()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.partNumber = try container.decode(String.self, forKey: .partNumber)
    }
}

Это просто оставляет самый верхний уровень. Мы создадим тип Containers, просто чтобы обернуть вещи.

struct Containers: Decodable {
    let containers: [Container]
    init(from decoder: Decoder) throws {
        self.containers = try decoder.decodeTitledElements(Container.self)
    }
}

И использовать его, декодировать верхний уровень Containers:

let containers = try JSONDecoder().decode(Containers.self, from: json)
print(containers.containers)

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

* 1034 Gist *

0 голосов
/ 10 января 2019

В этом случае мы не можем создать статические codable классы для этого JSON . Лучше используйте JSON serialization и восстановите его.

...