Search code examples
iosjsonswiftkvcgoogle-books

Can't get value from Google-Books JSON API


I'm using a bar code scanner to get data on a scanned book using the google books api. I successfully call the API and get a JSON object back.

I'm trying to get the book title which follows the path items.volumeInfo.title.

When I call valueForPath on the JSON object returned by the API and attempt to print it (the title), I end up printing:

Optional(( "A Dance with Dragons" ))

I can't seem to figure out how to actually get the string out of the printed optional. I tried as! String and jsonResult.valueForKeyPath("items.volumeInfo.title")!, but the first simply complained to me and the second only removed the optional and outside set of parentheses.

func getBookInfo(isbn: String) {
        var url: String = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn;
        var request: NSMutableURLRequest = NSMutableURLRequest()
        request.URL = NSURL(string: url)
        request.HTTPMethod = "GET"

        NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
            var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
            let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary

            if (jsonResult != nil) {

                println(jsonResult.valueForKeyPath("items.volumeInfo.title"))
                //self.json.setValue(jsonResult.valueForKeyPath("items.volumeInfo.title")!, forKey: "title")

            } else {
                GlobalConstants.AlertMessage.displayAlertMessage("Error fetching data from barcode, please try again.", view: self)
            }


        })
    }

Solution

  • The response you get from the API is an array of titles.

    I suggest using if let to unwrap the Optional value you get from KVC, and typecasting the result as a Swift array of Strings.

    Swift 1

    func getBookInfo(isbn: String) {
        var url: String = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn
        var request: NSMutableURLRequest = NSMutableURLRequest()
        request.URL = NSURL(string: url)
        request.HTTPMethod = "GET"
    
        NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
            var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
    
            if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? NSDictionary {
    
                if let arrayOfTitles = jsonResult.valueForKeyPath("items.volumeInfo.title") as? [String] {
                    let titles = ", ".join(arrayOfTitles)
                    println(titles)
                } else {
                    // error: no title found
                }
    
            } else {
    
                GlobalConstants.AlertMessage.displayAlertMessage("Error fetching data from barcode, please try again.", view: self)
    
            }
    
        })
    }
    
    getBookInfo("0553283685")  // prints "Hyperion"
    

    Swift 2

    For this version we're using NSURLSession because NSURLConnection is now deprecated.

    func getBookInfo(isbn: String) {
        let urlString = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn
        if let url = NSURL(string: urlString) {
            NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {data, _, error -> Void in
                if let error = error {
                    print(error.localizedDescription)
                } else {
                    if let data = data,
                        jsonResult = try? NSJSONSerialization.JSONObjectWithData(data, options: []),
                        arrayOfTitles = jsonResult.valueForKeyPath("items.volumeInfo.title") as? [String] {
                        let titles = arrayOfTitles.joinWithSeparator(", ")
                        print(titles)
                    } else {
                        GlobalConstants.AlertMessage.displayAlertMessage("Error fetching data from barcode, please try again.", view: self)
                    }
                }
            }).resume()
        }
    }
    
    getBookInfo("0553283685") // prints "Hyperion"
    

    Swift 3

    Same as Swift 2 with some syntax changes. I've also added the "authors" example, and I'm now using guard. Just for the sake of showing something different from the previous example.

    func getBookInfo(isbn: String) {
        guard let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=isbn:\(isbn)") else {
            print("the url is not valid")
            return
        }
        URLSession.shared().dataTask(with: url, completionHandler: {data, response, error -> Void in
            guard error == nil else {
                print(response)
                print(error!.localizedDescription)
                return
            }
            guard let data = data else {
                print("no error but no data")
                print(response)
                return
            }
            guard let jsonResult = try? JSONSerialization.jsonObject(with: data, options: []) else {
                print("the JSON is not valid")
                return
            }
            if let arrayOfTitles = jsonResult.value(forKeyPath: "items.volumeInfo.title") as? [String] {
                print(arrayOfTitles)
            }
            if let arrayOfAuthors = jsonResult.value(forKeyPath: "items.volumeInfo.authors") as? [[String]] {
                print(arrayOfAuthors)
            }
        }).resume()
    }
    
    getBookInfo(isbn: "0553283685")