使用顶级数据在 Swift 中解码对结构数组的 JSON 响应
Decode in Swift a JSON response to an array of struct using top level data
我收到 API 的 JSON 回复,看起来像这样:
{
"data": {
"author_id": "wxyz",
"author_name": "Will",
"language": "English",
"books": [
{"book_id":"abc1", "book_name":"BookA"},
{"book_id":"def2", "book_name":"BookB"},
{"book_id":"ghi3", "book_name":"BookC"}
]
}
}
目前我的 Book
结构如下所示:
struct Book: Codable {
let book_id: String
let book_name: String
}
但我想要这样的 Book
(来自顶级的数据):
struct Book: Codable {
let book_id: String
let book_name: String
let author: Author
let language: String
}
使用 Codable(/解码自定义类型),如何将上面的 JSON 响应直接转换为图书列表(而一些数据来自顶级对象)?
当我使用 decode 时,我可以自动将 books 数组解码为 Book 数组:
let books = try decoder.decode([Book].self, from: jsonData)
但是我找不到传递作者姓名和id的方法,或者语言,因为它在顶层
您可以使用自定义 init(decoder:)
来实现,但另一种方法是隐藏您坚持使用 JSON 模型的内部实现,并使用 lazy var
或计算得到。 Lazy var只会加载一次,这取决于你是否保留根。
struct Root: Codable {
//Hidde,
private let data: RootData
//Hidden
struct RootData: Codable {
let author_id: String
let author_name: String
let language: String
let books: [RootBook]
}
//Hidden
struct RootBook: Codable {
let book_id: String
let book_name: String
}
lazy var books: [Book] = {
let author = Author(id: data.author_id, name: data.author_name)
return data.books.map {
Book(id: [=10=].book_id, name: [=10=].book_name, author: author, language: data.language)
}
}()
var books2: [Book] {
let author = Author(id: data.author_id, name: data.author_name)
return data.books.map {
Book(id: [=10=].book_id, name: [=10=].book_name, author: author, language: data.language)
}
}
}
//Visible
struct Book: Codable {
let id: String
let name: String
let author: Author
let language: String
}
//Visible
struct Author: Codable {
let id: String
let name: String
}
使用:
do {
var root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
print("Books: \(root.books)") //Since it's a lazy var, it need to be mutable
} catch {
print("Error: \(error)")
}
或
do {
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
print("Books: \(root.books2)")
} catch {
print("Error: \(error)")
}
旁注:最简单的方法确实是坚持 JSON 模型。现在,拥有内部模型可能也很有趣,也就是说,你有自己的 Book
Class,你从 Root
初始化。因为明天,JSON 可能会改变(服务器改变等)。然后用于您的视图的模型(如何显示它们)也可能不同......
分离你的层,无论你想使用 MVC、MVVM、VIPER 等
编辑:
您可以覆盖 init(decoder:)
,但这是否会使代码更清晰?我发现它比以前的版本更难写(意思是,更难debug/modify?)
struct Root2: Codable {
let books: [Book2]
private enum TopLevelKeys: String, CodingKey {
case data
}
private enum SubLevelKeys: String, CodingKey {
case books
case authorId = "author_id"
case authorName = "author_name"
case language
}
private enum BoooKeys: String, CodingKey {
case id = "book_id"
case name = "book_name"
}
init(from decoder: Decoder) throws {
let topContainer = try decoder.container(keyedBy: TopLevelKeys.self)
let subcontainer = try topContainer.nestedContainer(keyedBy: SubLevelKeys.self, forKey: .data)
var bookContainer = try subcontainer.nestedUnkeyedContainer(forKey: .books)
var books: [Book2] = []
let authorName = try subcontainer.decode(String.self, forKey: .authorName)
let authorid = try subcontainer.decode(String.self, forKey: .authorId)
let author = Author(id: authorid, name: authorName)
let language = try subcontainer.decode(String.self, forKey: .language)
while !bookContainer.isAtEnd {
let booksubcontainer = try bookContainer.nestedContainer(keyedBy: BoooKeys.self)
let bookName = try booksubcontainer.decode(String.self, forKey: .name)
let bookId = try booksubcontainer.decode(String.self, forKey: .id)
books.append(Book2(book_id: bookId, book_name: bookName, author: author, language: language))
}
self.books = books
}
}
struct Book2: Codable {
let book_id: String
let book_name: String
let author: Author
let language: String
}
我收到 API 的 JSON 回复,看起来像这样:
{
"data": {
"author_id": "wxyz",
"author_name": "Will",
"language": "English",
"books": [
{"book_id":"abc1", "book_name":"BookA"},
{"book_id":"def2", "book_name":"BookB"},
{"book_id":"ghi3", "book_name":"BookC"}
]
}
}
目前我的 Book
结构如下所示:
struct Book: Codable {
let book_id: String
let book_name: String
}
但我想要这样的 Book
(来自顶级的数据):
struct Book: Codable {
let book_id: String
let book_name: String
let author: Author
let language: String
}
使用 Codable(/解码自定义类型),如何将上面的 JSON 响应直接转换为图书列表(而一些数据来自顶级对象)?
当我使用 decode 时,我可以自动将 books 数组解码为 Book 数组:
let books = try decoder.decode([Book].self, from: jsonData)
但是我找不到传递作者姓名和id的方法,或者语言,因为它在顶层
您可以使用自定义 init(decoder:)
来实现,但另一种方法是隐藏您坚持使用 JSON 模型的内部实现,并使用 lazy var
或计算得到。 Lazy var只会加载一次,这取决于你是否保留根。
struct Root: Codable {
//Hidde,
private let data: RootData
//Hidden
struct RootData: Codable {
let author_id: String
let author_name: String
let language: String
let books: [RootBook]
}
//Hidden
struct RootBook: Codable {
let book_id: String
let book_name: String
}
lazy var books: [Book] = {
let author = Author(id: data.author_id, name: data.author_name)
return data.books.map {
Book(id: [=10=].book_id, name: [=10=].book_name, author: author, language: data.language)
}
}()
var books2: [Book] {
let author = Author(id: data.author_id, name: data.author_name)
return data.books.map {
Book(id: [=10=].book_id, name: [=10=].book_name, author: author, language: data.language)
}
}
}
//Visible
struct Book: Codable {
let id: String
let name: String
let author: Author
let language: String
}
//Visible
struct Author: Codable {
let id: String
let name: String
}
使用:
do {
var root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
print("Books: \(root.books)") //Since it's a lazy var, it need to be mutable
} catch {
print("Error: \(error)")
}
或
do {
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
print("Books: \(root.books2)")
} catch {
print("Error: \(error)")
}
旁注:最简单的方法确实是坚持 JSON 模型。现在,拥有内部模型可能也很有趣,也就是说,你有自己的 Book
Class,你从 Root
初始化。因为明天,JSON 可能会改变(服务器改变等)。然后用于您的视图的模型(如何显示它们)也可能不同......
分离你的层,无论你想使用 MVC、MVVM、VIPER 等
编辑:
您可以覆盖 init(decoder:)
,但这是否会使代码更清晰?我发现它比以前的版本更难写(意思是,更难debug/modify?)
struct Root2: Codable {
let books: [Book2]
private enum TopLevelKeys: String, CodingKey {
case data
}
private enum SubLevelKeys: String, CodingKey {
case books
case authorId = "author_id"
case authorName = "author_name"
case language
}
private enum BoooKeys: String, CodingKey {
case id = "book_id"
case name = "book_name"
}
init(from decoder: Decoder) throws {
let topContainer = try decoder.container(keyedBy: TopLevelKeys.self)
let subcontainer = try topContainer.nestedContainer(keyedBy: SubLevelKeys.self, forKey: .data)
var bookContainer = try subcontainer.nestedUnkeyedContainer(forKey: .books)
var books: [Book2] = []
let authorName = try subcontainer.decode(String.self, forKey: .authorName)
let authorid = try subcontainer.decode(String.self, forKey: .authorId)
let author = Author(id: authorid, name: authorName)
let language = try subcontainer.decode(String.self, forKey: .language)
while !bookContainer.isAtEnd {
let booksubcontainer = try bookContainer.nestedContainer(keyedBy: BoooKeys.self)
let bookName = try booksubcontainer.decode(String.self, forKey: .name)
let bookId = try booksubcontainer.decode(String.self, forKey: .id)
books.append(Book2(book_id: bookId, book_name: bookName, author: author, language: language))
}
self.books = books
}
}
struct Book2: Codable {
let book_id: String
let book_name: String
let author: Author
let language: String
}