gpt4 book ai didi

json - 使用 swift Codable 解码以值作为键的 JSON

转载 作者:搜寻专家 更新时间:2023-10-31 08:16:03 28 4
gpt4 key购买 nike

我在解码 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 类中修复什么才能将键作为标题属性和这些类中的对象数组?

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。使这个 Encodable 是一个单独的问题。

处理未知值键的核心工具是可以处理任何字符串的 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)并解码它们:

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))
}
}

这就是大部分工作。我们可以使用此协议(protocol)使上层解码变得非常容易。只需实现 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 的顺序不同,并且在两次运行之间的顺序也可能不同。

Gist

关于json - 使用 swift Codable 解码以值作为键的 JSON,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54129682/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com