为具有失败响应的字典数组实现 Codable

Implement Codable for an Array of Dictionary with failable responses

我的 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 等,我在响应中得到这些键并仅保存未知键和值。 请建议如何使用 codable 解析它。

我已经阅读了这个答案以及答案的第三条评论中建议的变体。

这个答案展示了如何过滤数组中不正确的数据,以免丢失正确的数据。我正在尝试做类似的事情。

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

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

提前致谢。

这是一个广泛基于此 of Rob 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
}

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

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