简体   繁体   English

Swift:可编码用于复杂的JSON

[英]Swift: Codable for complex JSON

I get JSON response when I hit wiki API as shown below. 按下Wiki API时,我得到JSON响应,如下所示。 I find it complex to decode it. 我发现解码它很复杂。

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

I am interested only in array of WikiPage data from the above JSON where structure should look something like this. 我只对上述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)
    }
}

I am not able to dig further into query here as I get a data mismatch error. 由于出现数据不匹配错误,因此无法在此处进一步query Can I be able to decode without using extra classes/vars to hold unwanted data? 我可以在不使用额外的类/变量来保存不需要的数据的情况下进行解码吗? How do we decode in this case? 在这种情况下,我们该如何解码?

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

So I was able to get query to decode a container. 因此,我能够获得query以解码容器。 However, pages does not work because it contains and array not [String: Any]. 但是, pages不起作用,因为它包含且数组不是[String:Any]。 Look at this question and you can see that it is possible to decode nested values in a more direct manner like you want. 查看此问题 ,您会发现可以像您想要的那样以更直接的方式解码嵌套值。 However, those values are directly accessible, where the values you are trying to decode are in an array of pages so this method won't work for your JSON. 但是,这些值可以直接访问,您要解码的值位于页面数组中,因此该方法不适用于JSON。

For example what value should pageid , thumbnail , title , or description have? 例如, pageidthumbnailtitledescription应该pageid什么值? There are several pages in your JSON. JSON中有多个页面。 I don't think you want one object with those variables for every page? 我认为您不希望每个页面都有一个带有这些变量的对象吗?

In any case I think your best solution here is to nest your decoding as suggested in this answer . 无论如何,我认为您最好的解决方案是按照此答案的建议嵌套解码。 The idea is to decode the full object first (RawResponse) then just take your desired values and save them in a smaller object (Wikipage). 这个想法是先解码完整的对象(RawResponse),然后仅获取所需的值并将其保存在较小的对象中(Wikipage)。 I assumed you wanted the values for the first page. 我假设您想要第一页的值。

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

The syntax you're asking for is not possible. 您要求的语法是不可能的。

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

This decodes an Array of Wikipage. 这将解码Wikipage数组。 There is already a decoder for that in stdlib which doesn't do what you want. stdlib中已经有用于此功能的解码器,无法执行您想要的操作。 You can't replace it. 您不能替换它。 You need your own type that wraps it up. 您需要自己的类型来包装它。 That type doesn't have to do anything except wrap up the result, but it has to exist. 除了包装结果,该类型不需要执行任何操作,但是它必须存在。 Here's how you build it. 这是您如何构建它。

First, a WikiPage should be just the Wikipage portion. 首先,WikiPage应该只是Wikipage部分。 It should not try to know anything about its container. 它不应尝试了解有关其容器的任何信息。 So it would look something like this: 所以看起来像这样:

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

I've removed the optionality of things that don't need to be optional, and elevated things like URLs to proper types. 我删除了不需要是可选的东西的可选性,并将诸如URL之类的东西提升为适当的类型。 Notice the private helper structs (Thumbnail and Terms). 注意私有帮助器结构(缩略图和术语)。 These take care of nesting. 这些照顾嵌套。

The same approach applies to the top-level. 相同的方法适用于顶层。 There's no need to decode anything you don't want, but you do need a type to hold it. 不需要解码任何不需要的东西,但是您确实需要一种类型来保存它。

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

And finally to use it: 最后使用它:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM