P5y*_*cH0 4 json swift codable
我在解码JSON结构时遇到问题,我无法对其进行更改以使其更易于解码(它来自firebase)。
如何将以下JSON解码为对象?问题是如何转换“ 7E7-M001”。这是带有抽屉的容器的名称。抽屉名称也用作键。
{
"7E7-M001" : {
"Drawer1" : {
"101" : {
"Partnumber" : "F101"
},
"102" : {
"Partnumber" : "F121"
}
}
},
"7E7-M002": {
"Drawer1": {
"201": {
"Partnumber": "F201"
},
"202": {
"Partnumber": "F221"
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我必须在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"
}
}
Run Code Online (Sandbox Code Playgroud)
Rob*_*ier 10
首先,我将略作简化,以便我可以集中讨论此问题的重点。我将使所有内容不变,用结构替换类,仅实现Decodable。使此可编码成为一个单独的问题。
处理未知值键的中心工具是CodingKey,它可以处理任何字符串:
struct TitleKey: CodingKey {
let stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
Run Code Online (Sandbox Code Playgroud)
第二个重要工具是了解自己的头衔的能力。这意味着询问解码器“我们在哪里?” 那是当前编码路径中的最后一个元素。
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
}
}
Run Code Online (Sandbox Code Playgroud)
然后,我们需要一种以这种方式对“标题”元素进行解码的方法:
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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
这样,我们可以为这些“有标题的”事物发明一个协议并对其进行解码:
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))
}
}
Run Code Online (Sandbox Code Playgroud)
这就是大部分工作。我们可以使用此协议使高层解码非常容易。只需实施即可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
}
}
Run Code Online (Sandbox Code Playgroud)
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)
}
}
Run Code Online (Sandbox Code Playgroud)
那只是最高级。我们将创建一个Containers类型来包装所有内容。
struct Containers: Decodable {
let containers: [Container]
init(from decoder: Decoder) throws {
self.containers = try decoder.decodeTitledElements(Container.self)
}
}
Run Code Online (Sandbox Code Playgroud)
并使用它,解码顶级Containers:
let containers = try JSONDecoder().decode(Containers.self, from: json)
print(containers.containers)
Run Code Online (Sandbox Code Playgroud)
请注意,由于JSON对象不是按顺序保留的,因此数组可能与JSON的顺序不同,并且两次运行之间的顺序也可能不同。
| 归档时间: |
|
| 查看次数: |
1110 次 |
| 最近记录: |