为具有错误响应的字典数组实现可编码

时间:2019-01-12 15:11:33

标签: swift codable decodable

我的JSON答复如下:

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

从以上响应中,我想获取未知键及其值。未知密钥的值是一种自定义类型,称为Record,符合Codable协议。

我已经创建了用于解析数据的结构

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

因此,我想过滤响应中得到的所有其他键,例如event, created, user等,并仅保存未知键和值。 请建议如何使用可编码来解析此内容。

我已经仔细阅读了该答案以及答案第三条注释中建议的变体形式。 https://stackoverflow.com/a/46369152/8330469

此答案显示了如何过滤Array中的错误数据,以便不会丢失正确的数据。我正在尝试做类似的事情。

例如,我想丢弃event键,因为它的类型为String,而不是类型为Record

以上答案将丢弃整个词典,因为所有词典的数据都不正确,例如event。最后,我得到一个空数组。

谢谢。

1 个答案:

答案 0 :(得分:1)

这是一个广泛基于intriguing answerRob Napier的解决方案。

TitleKey和两个Decoder扩展的目标是将具有任意键的字典映射到将键作为title属性添加的数组。

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

我修改了decodeTitledElements函数,使其仅解码其值表示过滤其他键的RecordSuper结构的那些字典。

这是结构。

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
}

现在假设jsonData是JSON,Data解码JSON

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
let result = try decoder.decode(Root.self, from: jsonData
print(result.data)