Use swift Codable to decode JSON with values as keys

后端 未结 3 1782
别跟我提以往
别跟我提以往 2020-11-29 11:50

I have a problem decoding a JSON structure which I cannot change to make it easier to decode (it\'s coming from firebase)..

How do I decode the following JSON into o

3条回答
  •  甜味超标
    2020-11-29 12:22

    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.

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

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

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

    And to use it, decode the top level Containers:

    let containers = try JSONDecoder().decode(Containers.self, from: json)
    print(containers.containers)
    

    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.

    Gist

提交回复
热议问题