Swift:可对复杂JSON进行编码

Swift:可对复杂JSON进行编码,json,swift,codable,decodable,Json,Swift,Codable,Decodable,当我点击wiki API时,我得到JSON响应,如下所示。我发现解码它很复杂 { "continue": { "picontinue": 452160, "continue": "||pageterms" }, "query": { "pages": [ { "pageid": 164053, "ns": 0,

当我点击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)
    }
}
由于出现数据不匹配错误,我无法在此处进一步深入查看
查询。我能在不使用额外类/变量来保存不需要的数据的情况下解码吗?在这种情况下我们如何解码

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

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

例如,
pageid
thumbnail
title
description
应该有什么值?JSON中有几个页面。我想你不希望每个页面都有一个带有这些变量的对象吗

在任何情况下,我认为最好的解决方案是按照中的建议嵌套解码。其思想是首先解码完整对象(RawResponse),然后只获取所需的值并将其保存在较小的对象(Wikipage)中。我假设您想要第一页的值

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

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

例如,
pageid
thumbnail
title
description
应该有什么值?JSON中有几个页面。我想你不希望每个页面都有一个带有这些变量的对象吗

在任何情况下,我认为最好的解决方案是按照中的建议嵌套解码。其思想是首先解码完整对象(RawResponse),然后只获取所需的值并将其保存在较小的对象(Wikipage)中。我假设您想要第一页的值

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!)
这将解码Wikipage的数组。在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

您要求的语法是不可能的

let wiki = try decoder.decode([Wikipage].self, from: data!)
这将解码Wikipage的数组。在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

试试这个:它会为你做的。这会创建我不需要的额外类。我已经把这个问题告诉了你自己,我想你至少需要找到顶级的,Response/Root(不管你怎么称呼它)和Query。如果按预期使用Decodable,则不会出现问题,但这取决于您自己,但伞形结构有什么问题?使用嵌套容器编写自定义逻辑比声明单独的结构要费劲得多(效率更低)。试试看:它会(尝试)为您这样做。这将创建我不需要的额外类。我已经把这个问题告诉了你自己,我想你至少需要找到顶级的,Response/Root(不管你怎么称呼它)和Query。如果按预期使用Decodable,则不会出现问题,但这取决于您自己,但伞形结构有什么问题?使用嵌套容器编写自定义逻辑比声明单独的结构要费力得多(但效率较低)。