Implement Codable for an Array of Dictionary with failable responses

为君一笑 提交于 2019-12-02 09:37:56

问题


My JSON response is the following:

{
    "data": [
        {
            "unknown-key-c3e7f0": {
                "date_time": 1546944854000,
                "medication": "f4f25ea4-0607-4aac-b85a-edf40cc7d5b6",
                "record": {
                    "status": "never"
                }
            },
            "unknown-key-619d40": {
                "date_time": 1546944854000,
                "medication": "deef2278-f176-418f-ac34-c65fa54e712c",
                "record": {
                    "status": "always"
                }
            },
            "event": "06b445b9-dae0-48a1-85e4-b9f48c9a2349",
            "created": 1546949155020,
            "user": "8fb3fcd1-ffe6-4fd9-89b8-d22b1653cb6a",
            "id": "1546944855000",
            "type": "compliance"
        },
        {
            "unknown-key-619d40": {
                "date_time": 1546944975000,
                "medication": "deef2278-f176-418f-ac34-c65fa54e712c",
                "record": {
                    "status": "sometimes"
                }
            },
            "event": "7309d8e9-b71c-4068-b278-0ae6d91a57a6",
            "created": 1546946798407,
            "user": "8fb3fcd1-ffe6-4fd9-89b8-d22b1653cb6a",
            "id": "1546944975000",
            "type": "compliance"
        }
}

From the above response, I want to get the unknown keys and their values. The values of the unknown keys are of a custom type called Record which conforms to the Codable protocol.

I have created this struct for the parsing the data

struct RecordSuper: Codable
{
    var data: [[String: Record]]
}

So, I want to filter all other keys like event, created, user, etc which I am getting in the response and save only the unknown keys and the values. Please suggest how to parse this using codable.

I have gone through this answer as well as the variation suggested in the third comment of the answer. https://stackoverflow.com/a/46369152/8330469

This answer shows how to filter the incorrect data in an Array so that the correct data is not lost. I am trying to do something similar.

For example, I want to discard the event key because it is of type String and not of type Record.

The above answer will discard the whole dictionary because all the dictionaries are have incorrect data like event. And in the end, I get an empty array.

Thanks in advance.


回答1:


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)


来源:https://stackoverflow.com/questions/54160875/implement-codable-for-an-array-of-dictionary-with-failable-responses

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!