Swift:复杂的可编码 JSON

Swift: Codable for complex JSON

当我点击 wiki API 时,我得到 JSON 响应,如下所示。我发现解码它很复杂。

{
    "continue": {
        "picontinue": 452160,
        "continue": "||pageterms"
    },
    "query": {
        "pages": [
            {
                "pageid": 164053,
                "ns": 0,
                "title": "Abdullah II of Jordan",
                "index": 2,
                "terms": {
                    "description": [
                        "King of the Hashemite Kingdom of Jordan"
                    ]
                }
            },
            {
                "pageid": 348097,
                "ns": 0,
                "title": "Abdullah Ahmad Badawi",
                "index": 9,
                "terms": {
                    "description": [
                        "Malaysian politician"
                    ]
                }
            },
            {
                "pageid": 385658,
                "ns": 0,
                "title": "Abdelaziz Bouteflika",
                "index": 8,
                "thumbnail": {
                    "source": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/Abdelaziz_Bouteflika_casts_his_ballot_in_May_10th%27s_2012_legislative_election_%28cropped%29.jpg/37px-Abdelaziz_Bouteflika_casts_his_ballot_in_May_10th%27s_2012_legislative_election_%28cropped%29.jpg",
                    "width": 37,
                    "height": 50
                },
                "terms": {
                    "description": [
                        "President of Algeria"
                    ]
                }
            },
            {
                "pageid": 452160,
                "ns": 0,
                "title": "Abdul Qadeer Khan",
                "index": 7,
                "terms": {
                    "description": [
                        "Pakistani nuclear scientist"
                    ]
                }
            },
            {
                "pageid": 2028438,
                "ns": 0,
                "title": "Abdelbaset al-Megrahi",
                "index": 6,
                "terms": {
                    "description": [
                        "Libyan mass murderer"
                    ]
                }
            },
            {
                "pageid": 4709709,
                "ns": 0,
                "title": "Abdul",
                "index": 1,
                "terms": {
                    "description": [
                        "family name"
                    ]
                }
            },
            {
                "pageid": 18950786,
                "ns": 0,
                "title": "Abdul Hamid II",
                "index": 5,
                "thumbnail": {
                    "source": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Abdul_Hamid_2.jpg/35px-Abdul_Hamid_2.jpg",
                    "width": 35,
                    "height": 50
                },
                "terms": {
                    "description": [
                        "34th sultan of the Ottoman Empire"
                    ]
                }
            },
            {
                "pageid": 19186951,
                "ns": 0,
                "title": "Abdullah of Saudi Arabia",
                "index": 4,
                "terms": {
                    "description": [
                        "former King of Saudi Arabia"
                    ]
                }
            },
            {
                "pageid": 25955055,
                "ns": 0,
                "title": "Abdullah of Pahang",
                "index": 10,
                "terms": {
                    "description": [
                        "Sultan of Pahang"
                    ]
                }
            },
            {
                "pageid": 36703624,
                "ns": 0,
                "title": "Abdel Fattah el-Sisi",
                "index": 3,
                "thumbnail": {
                    "source": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Abdel_Fattah_el-Sisi_in_2017.jpg/39px-Abdel_Fattah_el-Sisi_in_2017.jpg",
                    "width": 39,
                    "height": 50
                },
                "terms": {
                    "description": [
                        "Current President of Egypt"
                    ]
                }
            }
        ]
    }
}

我只对上述 JSON 中的 WikiPage 数据数组感兴趣,其中的结构应该类似于这样。

struct Wikipage: Decodable {
    var pageid: Int?
    var thumbnail: String?
    var title: String?
    var description: String?
    enum CodingKeys: String, CodingKey {
        case query
        case pages
        case terms
        case description
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let query = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .query)
        print(query)
    }
}

由于出现数据不匹配错误,我无法在此处进一步深入 query。我可以在不使用额外的 classes/vars 来保存不需要的数据的情况下进行解码吗?在这种情况下我们如何解码?

        let wiki = try decoder.decode([Wikipage].self, from: data!)

所以我能够 query 解码一个容器。但是,pages 不起作用,因为它包含数组而不是 [String: Any]。查看 ,您会发现可以像您希望的那样以更直接的方式解码嵌套值。但是,这些值可以直接访问,您尝试解码的值位于页面数组中,因此此方法不适用于您的 JSON.

例如 pageidthumbnailtitledescription 应该有什么值?您的 JSON 中有多个页面。我认为您不希望每个页面都有一个包含这些变量的对象?

无论如何,我认为最好的解决方案是按照 中的建议嵌套解码。这个想法是首先解码完整的对象(RawResponse)然后只取你想要的值并将它们保存在一个较小的对象(维基页面)中。我假设您想要第一页的值。

struct RawResponse: Codable {
    let query: Query

    enum CodingKeys: String, CodingKey {
        case query
    }
}

struct Query: Codable {
    let pages: [Page]
}

struct Page: Codable {
    let pageid: Int?
    let title: String?
    let terms: Terms?
    let thumbnail: Thumbnail?
}

struct Terms: Codable {
    let description: [String]
}

struct Thumbnail: Codable {
    let source: String
    let width, height: Int
}


struct Wikipage: Decodable {

    var pageid: Int?
    var thumbnail: String?
    var title: String?
    var description: String?

    init(from decoder: Decoder) throws {
        let rawResponse = try RawResponse(from: decoder)

        let pageZero = rawResponse.query.pages.first

        self.pageid = pageZero?.pageid
        self.thumbnail = pageZero?.thumbnail?.source
        self.title = pageZero?.title
        self.description = pageZero?.terms?.description.first
    }
}

您要求的语法不存在。

let wiki = try decoder.decode([Wikipage].self, from: data!)

这将解码一组维基页面。在 stdlib 中已经有一个解码器,它不会做你想要的。你无法取代它。你需要你自己的类型来包装它。该类型除了包装结果外不需要做任何事情,但它必须存在。这是你如何构建它。

首先,WikiPage 应该只是 Wikipage 部分。它不应该试图了解有关其容器的任何信息。所以它看起来像这样:

struct Wikipage {
    let pageid: Int
    let thumbnail: URL?
    let title: String
    let description: String
}

extension Wikipage: Decodable {
    private enum CodingKeys: String, CodingKey {
        case pageid
        case title
        case thumbnail
        case terms
    }

    private struct Thumbnail: Decodable { let source: String }

    private struct Terms: Decodable { let description: [String] }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.pageid = try container.decode(Int.self, forKey: .pageid)
        self.title = try container.decode(String.self, forKey: .title)

        let source = try container.decodeIfPresent(Thumbnail.self,
                                                   forKey: .thumbnail)?.source
        self.thumbnail = source.flatMap(URL.init(string:))

        self.description = try container.decode(Terms.self,
                                                forKey: .terms).description.first ?? ""
    }
}

我删除了不需要可选的内容的可选性,并将 URL 等内容提升为正确的类型。注意私有助手结构(缩略图和术语)。这些负责嵌套。

同样的方法适用于顶层。不需要解码任何你不想要的东西,但你确实需要一个类型来保存它。

struct WikipageResult: Decodable {
    let pages: [Wikipage]

    private enum CodingKeys: String, CodingKey {
        case query
    }
    private struct Query: Decodable {
        let pages: [Wikipage]
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.pages = try container.decode(Query.self, forKey: .query).pages
    }
}

最后使用它:

let pages = try JSONDecoder().decode(WikipageResult.self, from: json).pages