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