Search code examples
jsonswiftparsingitunes

Unwrapping JSON from Itunes API - IOS App


Having an issue with my program. I would appreciate it if someone could help out. I have tried for weeks to parse the JSON files fetched from the iTunes API (itunes.apple.com/search?term=song+you+want+to+search&entity=songTrack).

However, my answers are never displayed on my tableview and an error always shows up in the terminal:

"2017-11-14 17:25:28.809190+0100 Itunes Learning[32409:6240818] [MC] Lazy loading NSBundle MobileCoreServices.framework
2017-11-14 17:25:28.810264+0100 Itunes Learning[32409:6240818] [MC] Loaded MobileCoreServices.framework
2017-11-14 17:25:28.823734+0100 Itunes Learning[32409:6240818] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/cyprianzander/Library/Developer/CoreSimulator/Devices/D52FD9D5-B6E4-4CE0-99E4-6E0EE15A680D/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
2017-11-14 17:25:29.875534+0100 Itunes Learning[32409:6240900] Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
(lldb) "

This is approximately how the JSON file is set up:

{“resultCount” : 50, “results”: [ {“trackName”:”name”, ”artistName”:”name2”}, {“trackName”:”name3”, “artistName”:”name4”} ] }

(An array of objects inside an array - meaning the first object is on the far outside).

I have tried my function with another API, which did work. I have the feeling that the main reason as to why this happens, is because the iTunes API JSON file is very complex. It is an assortment of very long objects inside an array, which is inside a smaller list of objects. However, the other one was only and array of objects.

Here is my code: (I have noticed that the problem occurs while parsing the data I need. The only thing I need to know is how to properly unwrap my JSON file)

func parseData(searchTerm: String) {
    fetchedSong = []
    let itunesSearchTerm = searchTerm.replacingOccurrences(of: " ", with: "+", options: .caseInsensitive, range: nil)
    let escapedSearchTerm = itunesSearchTerm.addingPercentEncoding(withAllowedCharacters: [])!
    let urlString = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&entity=song"
    let url = URL(string: urlString)!
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let error = error {
            // If there is an error in the web request, print it to the console
            print(error)
            return
        }

        else {
            do {
                let fetchedData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSArray
                print(fetchedData)
                for eachFetchedSong in fetchedData {
                    let eachSong = eachFetchedSong as! [String: Any]
                    let song = eachSong["trackName"] as! String
                    let artist = eachSong["artistName"] as! String
                    self.fetchedSong.append(songs(song: song, artist : artist))

                }
                self.SongTableView.reloadData()

            }
            catch {
                print("An error occured while decoding the JSON object")
            }

        }
    }.resume()
}

If anyone could help me, I would be extremely happy, especially because I have been stuck with this for three weeks, continuously trying different techniques (this one seemed the most successful).


Solution

  • Your JSON data is not an array. It is a dictionary with two key/value pairs. The first is the key "resultCount" with a value of 50, and the second is the key "results" with an array as its value.

    Never use as! when parsing JSON, since this will crash your app if you get an unexpected result. Don't use .mutableLeaves unless you can explain to us what it does and why you need it. Don't use NSArray in your Swift code.

    Handling one error and crashing on others is pointless. I'd write

    if let fetchedDict = try? JSONSerialization(...) as? [String:Any], 
       let fetchedArray = fetchedDict ["results"] as? [[String:Any]] {
        for dict in fetchedArray {
            if let song = dict ["trackName"] as? String, 
               let artist = dict ["artistName"] as? String {
               ...
            }
        }
    }