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

Decode JSON in Swift with changing Key at beginning

我目前正在学习 Swift,我想创建一个小应用程序,通过 ISBN 从 Openlibrary API 获取关于一本书的数据。 这是我用来获取数据的查询: 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 编号。

目前,我有一个文件“Books.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 编号创建一个案例...:(

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

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

或者编写自定义初始化程序来分隔 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。

不小心将所有内容都声明为可选是一种不好的做法。

PS:

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

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