Swift:可对复杂JSON进行编码
当我点击wiki API时,我得到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,
{
"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,则不会出现问题,但这取决于您自己,但伞形结构有什么问题?使用嵌套容器编写自定义逻辑比声明单独的结构要费力得多(但效率较低)。