使用 Swift 中的 Decodable 和 CodingKeys 解析 JSON 4
Parsing JSON using Decodable and CodingKeys in Swift 4
我正在尝试解析从 URL 中检索的大型 JSON 字符串。我用来测试的JSON如下:
let json = """
{
"feed": {
"title": "Harry Potter",
"test": "I dont want this value",
"results": [
{
"author": "JK Rowling",
"artworkURL": "A url",
"genres": [
{
"name": "Fantasy"
},
{
"name": "Scifi"
}
],
"name": "Goblet of Fire",
"releaseDate": "2000-07-08"
},
{
"author": "JK Rowling",
"artworkURL": "A url",
"genres": [
{
"name": "Fantasy"
},
{
"name": "Scifi"
}
],
"name": "Half Blood Prince",
"releaseDate": "2009-07-15"
}
]
}
}
""".data(using: .utf8)!
我有几个数据结构可以将数据放入:
struct Genre: Decodable {
let name: String
}
struct Book: Decodable {
let author: String
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
}
struct BookCollection {
let title: String
let books: [Book]
enum CodingKeys: String, CodingKey {
case feed
}
enum FeedKeys: String, CodingKey {
case title, results
}
enum ResultKeys: String, CodingKey {
case author, artworkURL, genres, name, releaseDate
}
enum GenreKeys: String, CodingKey {
case name
}
}
extension BookCollection: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let feed = try values.nestedContainer(keyedBy: FeedKeys.self,
forKey: .feed)
self.title = try feed.decode(String.self, forKey: .title)
self.books = try feed.decode([Track].self, forKey: .results)
}
}
然后我打印出这样的信息:
do {
let response = try JSONDecoder().decode(BookCollection.self, from: json)
for book in response.books {
print(book.genres)
}
} catch {
print(error)
}
成功打印除流派以外的所有信息。这给了我一个 genres 数组,但我做不到
book.genres.name
访问名称。我必须使用:
book.genres[0]
它只给了我第一个索引的结果。
有没有一种方法可以完善我的 BookCollection
扩展中的 JSON 解码,然后利用 book.genres.name
?
谢谢
如果你真的需要额外的 name
属性,你可以在新的扩展中这样做:
extension Array where Element == Genre {
var name: [String] {
return self.map { [=10=].name }
}
}
这会将前面提到的 name
属性 添加到 每个 [Genre]
值,包括您的 Book
类型。只需确保这确实是您所追求的(如果将此扩展声明为 private
,那么它将在相应的 swift 文件中可用)。
为了消除使用许多枚举编码键和手动解码类型的需要,您可以将数据结构更改为映射 JSON 结构格式。注意下面的代码,结构体不需要嵌套,也可以并列。此代码已使用您编码的 JSON 数据
进行测试
public struct HarryPotterFeed: Codable {
public let feed: BookCollection
public struct BookCollection: Codable {
public let title: String
public let books: [Book]
// map properties books to json's "results"
enum CodingKeys: String, CodingKey {
case title // needs to come along
case books = "results"
}
public struct Book: Codable {
public let author, name, artworkURL, releaseDate : String
public let genres: [Genre]
public struct Genre: Codable {
public let name: String
}
}
}
}
// Decode JSON
do {
let response = try JSONDecoder().decode(HarryPotterFeed.self, from: json)
for book in response.feed.books {
for name in book.genres {
print(name)
}
}
} catch {
print("PROBLEM DECODING JSON \(error)")
}
我正在尝试解析从 URL 中检索的大型 JSON 字符串。我用来测试的JSON如下:
let json = """
{
"feed": {
"title": "Harry Potter",
"test": "I dont want this value",
"results": [
{
"author": "JK Rowling",
"artworkURL": "A url",
"genres": [
{
"name": "Fantasy"
},
{
"name": "Scifi"
}
],
"name": "Goblet of Fire",
"releaseDate": "2000-07-08"
},
{
"author": "JK Rowling",
"artworkURL": "A url",
"genres": [
{
"name": "Fantasy"
},
{
"name": "Scifi"
}
],
"name": "Half Blood Prince",
"releaseDate": "2009-07-15"
}
]
}
}
""".data(using: .utf8)!
我有几个数据结构可以将数据放入:
struct Genre: Decodable {
let name: String
}
struct Book: Decodable {
let author: String
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
}
struct BookCollection {
let title: String
let books: [Book]
enum CodingKeys: String, CodingKey {
case feed
}
enum FeedKeys: String, CodingKey {
case title, results
}
enum ResultKeys: String, CodingKey {
case author, artworkURL, genres, name, releaseDate
}
enum GenreKeys: String, CodingKey {
case name
}
}
extension BookCollection: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let feed = try values.nestedContainer(keyedBy: FeedKeys.self,
forKey: .feed)
self.title = try feed.decode(String.self, forKey: .title)
self.books = try feed.decode([Track].self, forKey: .results)
}
}
然后我打印出这样的信息:
do {
let response = try JSONDecoder().decode(BookCollection.self, from: json)
for book in response.books {
print(book.genres)
}
} catch {
print(error)
}
成功打印除流派以外的所有信息。这给了我一个 genres 数组,但我做不到
book.genres.name
访问名称。我必须使用:
book.genres[0]
它只给了我第一个索引的结果。
有没有一种方法可以完善我的 BookCollection
扩展中的 JSON 解码,然后利用 book.genres.name
?
谢谢
如果你真的需要额外的 name
属性,你可以在新的扩展中这样做:
extension Array where Element == Genre {
var name: [String] {
return self.map { [=10=].name }
}
}
这会将前面提到的 name
属性 添加到 每个 [Genre]
值,包括您的 Book
类型。只需确保这确实是您所追求的(如果将此扩展声明为 private
,那么它将在相应的 swift 文件中可用)。
为了消除使用许多枚举编码键和手动解码类型的需要,您可以将数据结构更改为映射 JSON 结构格式。注意下面的代码,结构体不需要嵌套,也可以并列。此代码已使用您编码的 JSON 数据
进行测试public struct HarryPotterFeed: Codable {
public let feed: BookCollection
public struct BookCollection: Codable {
public let title: String
public let books: [Book]
// map properties books to json's "results"
enum CodingKeys: String, CodingKey {
case title // needs to come along
case books = "results"
}
public struct Book: Codable {
public let author, name, artworkURL, releaseDate : String
public let genres: [Genre]
public struct Genre: Codable {
public let name: String
}
}
}
}
// Decode JSON
do {
let response = try JSONDecoder().decode(HarryPotterFeed.self, from: json)
for book in response.feed.books {
for name in book.genres {
print(name)
}
}
} catch {
print("PROBLEM DECODING JSON \(error)")
}