在 Swift 中解码 JSON,并在开始时更改 Key



我目前正在学习 Swift,我想创建一个小应用程序,通过 Openlibrary API 的 ISBN 获取有关书籍的数据。 这是我用来获取数据的查询: https://openlibrary.org/api/books?bibkeys=ISBN:9783791504650&format=json&jscmd=data

现在返回的 JSON 如下所示:

{"ISBN:9783791504650": {"publishers": [{"name": "Dressler"}], "identifiers": {"isbn_13": ["9783791504650"], "openlibrary": ["OL8935767M"], "isbn_10": ["3791504657"], "librarything": ["1653"], "goodreads": ["292110"]}, "weight": "1.8 pounds", "title": "Tintenherz", "url": "https://openlibrary.org/books/OL8935767M/Tintenherz", "number_of_pages": 573, "cover": {"small": "https://covers.openlibrary.org/b/id/1027329-S.jpg", "large": "https://covers.openlibrary.org/b/id/1027329-L.jpg", "medium": "https://covers.openlibrary.org/b/id/1027329-M.jpg"}, "subject_places": [{"url": "https://openlibrary.org/subjects/place:italy", "name": "Italy"}], "subjects": [{"url": "https://openlibrary.org/subjects/fathers_and_daughters", "name": "Fathers and daughters"}, {"url": "https://openlibrary.org/subjects/characters_in_literature", "name": "Characters in literature"}, {"url": "https://openlibrary.org/subjects/magic", "name": "Magic"}, {"url": "https://openlibrary.org/subjects/storytelling", "name": "Storytelling"}, {"url": "https://openlibrary.org/subjects/fantasy", "name": "Fantasy"}, {"url": "https://openlibrary.org/subjects/bookbinding", "name": "Bookbinding"}, {"url": "https://openlibrary.org/subjects/fiction", "name": "Fiction"}, {"url": "https://openlibrary.org/subjects/books_and_reading", "name": "Books and reading"}, {"url": "https://openlibrary.org/subjects/bookbinders", "name": "Bookbinders"}, {"url": "https://openlibrary.org/subjects/authorship", "name": "Authorship"}, {"url": "https://openlibrary.org/subjects/characters_and_characteristics_in_literature", "name": "Characters and characteristics in literature"}, {"url": "https://openlibrary.org/subjects/juvenile_fiction", "name": "Juvenile fiction"}, {"url": "https://openlibrary.org/subjects/kidnapping", "name": "Kidnapping"}], "subject_people": [{"url": "https://openlibrary.org/subjects/person:meggie", "name": "Meggie"}, {"url": "https://openlibrary.org/subjects/person:mo", "name": "Mo"}, {"url": "https://openlibrary.org/subjects/person:dustfinger", "name": "Dustfinger"}, {"url": "https://openlibrary.org/subjects/person:capricorn", "name": "Capricorn"}, {"url": "https://openlibrary.org/subjects/person:basta", "name": "Basta"}, {"url": "https://openlibrary.org/subjects/person:mortola", "name": "Mortola"}, {"url": "https://openlibrary.org/subjects/person:fenoglio", "name": "Fenoglio"}, {"url": "https://openlibrary.org/subjects/person:elinor", "name": "Elinor"}, {"url": "https://openlibrary.org/subjects/person:resa", "name": "Resa"}, {"url": "https://openlibrary.org/subjects/person:the_shadow", "name": "The Shadow"}], "key": "/books/OL8935767M", "authors": [{"url": "https://openlibrary.org/authors/OL2704045A/Cornelia_Funke", "name": "Cornelia Funke"}], "publish_date": "2003", "ebooks": [{"formats": {}, "preview_url": "https://archive.org/details/tintenherz00funk", "availability": "restricted"}]}}

如您所见,JSON 以一个键开头,其中包括给定书籍的 ISBN 编号。

目前,我有一个文件"书籍.swift",看起来像这样:


struct Books: Codable {
let isbn: Isbn?
enum CodingKeys: String, CodingKey {
case isbn
}
}
struct Isbn: Codable {
let publishers: [Publisher]?
let identifiers: Identifiers?
let weight: String?
let title: String?
let url: String?
let numberOfPages: Int?
let cover: Cover?
let subjectPlaces: [Author]?
let subjects: [Author]?
let subjectPeople: [Author]?
let key: String?
let authors: [Author]?
let publishDate: String?
let ebooks: [Ebook]?
enum CodingKeys: String, CodingKey {
case publishers = "publishers"
case identifiers = "identifiers"
case weight = "weight"
case title = "title"
case url = "url"
case numberOfPages = "number_of_pages"
case cover = "cover"
case subjectPlaces = "subject_places"
case subjects = "subjects"
case subjectPeople = "subject_people"
case key = "key"
case authors = "authors"
case publishDate = "publish_date"
case ebooks = "ebooks"
}
}
// MARK: - Author
struct Author: Codable {
let url: String?
let name: String?
enum CodingKeys: String, CodingKey {
case url = "url"
case name = "name"
}
}
// MARK: - Cover
struct Cover: Codable {
let small: String?
let large: String?
let medium: String?
enum CodingKeys: String, CodingKey {
case small = "small"
case large = "large"
case medium = "medium"
}
}
// MARK: - Ebook
struct Ebook: Codable {
let formats: Formats?
let previewURL: String?
let availability: String?
enum CodingKeys: String, CodingKey {
case formats = "formats"
case previewURL = "preview_url"
case availability = "availability"
}
}
// MARK: - Formats
struct Formats: Codable {
}
// MARK: - Identifiers
struct Identifiers: Codable {
let isbn13: [String]?
let openlibrary: [String]?
let isbn10: [String]?
let librarything: [String]?
let goodreads: [String]?
enum CodingKeys: String, CodingKey {
case isbn13 = "isbn_13"
case openlibrary = "openlibrary"
case isbn10 = "isbn_10"
case librarything = "librarything"
case goodreads = "goodreads"
}
}
// MARK: - Publisher
struct Publisher: Codable {
let name: String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
}

为此,我使用了在线转换器,但它还包括我用来获取示例数据的书的 ISBN。

以下是请求开始的地方:

@IBAction func sendISBNSearchRequest(_ sender: Any) {

bookDataArray = [] //Empty Array, so there is no interfering old Data

let isbnUserInput: String = isbnInputfield.text!    //Read UserInput from Textinputfield and save it into a String
self.loadingIndicator.startAnimating()

if (isbnUserInput.isNumeric && (isbnUserInput.count == 10 || isbnUserInput.count == 13)){
// Checks if user input only contains numbers as ISBN-Numbers only consists of numbers, not characters
// Also Check if it is a valid ISBN Number with 10 or 13 Numbers

let searchURL = "https://openlibrary.org/api/books?bibkeys=ISBN:(isbnUserInput)&format=json&jscmd=data"
guard let url = URL(string: searchURL) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared

let task = session.dataTask(with: urlRequest) { data, response, error in
guard let data = data else {
return
}
let response = response as? HTTPURLResponse

if (response?.statusCode==200){
do{
let object = try JSONDecoder().decode(Books.self, from: data)
print(object)
DispatchQueue.main.async {
self.loadingIndicator.stopAnimating()
}
return

}catch{
print(error)
}
}
else{
self.loadingIndicator.stopAnimating()
}
}
task.resume()
}
else{
self.loadingIndicator.stopAnimating()
}

}

有没有办法让它适用于每个请求/ISBN 编号? 我还在学习,目前我看到的唯一方法是为每个现有的 ISBN 编号创建一个案例...... :(

或者编写自定义初始值设定项来分隔 ISBN 编号

struct Books: Decodable {
let isbn: String
let book : Isbn
init(from decoder : Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode([String:Book].self)
let key = data.keys.first!
isbn = key.components(separatedBy: ":").last!
book = data[key]!
}
}
<小时 />
do {
let object = try JSONDecoder().decode(Books.self, from: data)
print(object.isbn)
...

您可以通过添加.convertFromSnakeCase键解码策略并使结构成员的名称符合转换规则来摆脱 CodingKeys。

粗心大意地将所有内容声明为可选内容是一种不好的做法。

附注:

嘿 openlibrary.org,为什么不更合适

{"ISBN":"9783791504650","item":{"publishers":...

使用字典类型[String: Isbn]而不是类型Books的对象:

let dictionary = try JSONDecoder().decode([String:Isbn].self, from: data)
print(dictionary["ISBN:(isbnNumber)"])

最新更新