我在解码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" } }
首先,我将略作简化,以便我可以集中讨论此问题的重点。我将使所有内容不变,用结构替换类,仅实现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:)。
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 有点不同,因为它是叶节点并且还有其他要解码的内容。
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类型来包装所有内容。
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的顺序不同,并且两次运行之间的顺序也可能不同。
要旨