Implement Codable for an Array of Dictionary with failable responses

一曲冷凌霜 提交于 2019-12-02 04:25:47

This is a solution widely based on this intriguing answer of Rob Napier.

The goal of TitleKey and the two Decoder extensions is to map dictionaries with arbitrary keys to arrays adding the key as title property.

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 titles.allKeys.compactMap { title in
            return try? titles.decode(Element.self, forKey: title)
        }
    }
}

I modified the decodeTitledElements function to decode only those dictionaries whose value represents the RecordSuper struct filtering the other keys.

Here are the structs.

struct Root : Decodable {
    let data : [Containers]
}

struct Containers: Decodable {
    let containers: [RecordSuper]
    init(from decoder: Decoder) throws {
        self.containers = try decoder.decodeTitledElements(RecordSuper.self)
    }
}

struct RecordSuper : Decodable {
    let title : String
    let dateTime : Date
    let medication : String
    let record : Record

    enum CodingKeys: String, CodingKey {
        case dateTime = "date_time", medication, record
    }

    init(from decoder: Decoder) throws {
        self.title = try decoder.currentTitle()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.dateTime = try container.decode(Date.self, forKey: .dateTime)
        self.medication = try container.decode(String.self, forKey: .medication)
        self.record = try container.decode(Record.self, forKey: .record)
    }
}

struct Record : Decodable {
    let status : String
}

Now decode the JSON assuming jsonData is the JSON as Data

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
let result = try decoder.decode(Root.self, from: jsonData
print(result.data)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!