且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用Swift Codable解码以值作为键的JSON

更新时间:2021-12-20 00:23:39

首先,我将做一些简单的简化,以便将重点放在这个问题的重点上.我将使所有内容不可变,用结构替换类,并仅实现Decodable.使其成为可编码状态是一个单独的问题.

First I'm going to make some slight simplifications so I can focus on the important points of this question. I'm going to make everything immutable, replace the classes with structs, and only implement Decodable. Making this Encodable is a separate issue.

用于处理未知值键的中间工具是CodingKey,它可以处理任何字符串:

The central tool for handling unknown value keys is a CodingKey that can handle any string:

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

第二个重要工具是知道自己的标题的能力.这意味着向解码器询问我们在哪里?"那是当前编码路径中的最后一个元素.

The second important tool is the ability to know your own title. That means asking the decoder "where are we?" That's the last element in the current coding path.

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

然后我们需要一种方法来对以标题"命名的元素进行解码:

And then we need a way to decode elements that are "titled" this way:

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

有了这一点,我们可以为这些有标题的"事物发明一种协议并将其解码:

With that, we can invent a protocol for these "titled" things and decode them:

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:).

And that's most of the work. We can use this protocol to make decoding pretty easy for the upper-level layers. Just implement 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 is a little different since it's a leaf node and has other things to decode.

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类型只是为了包装内容.

That just leaves the very top level. We'll create a Containers type just to wrap things up.

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的顺序不同,并且在两次运行之间的顺序也可能不同.

Note that since JSON objects are not order-preserving, the arrays may not be in the same order as the JSON, and may not be in the same order between runs.

要点