I am learning to decode data with JSON in an Xcode playground but can't figure out what's wrong with my code, I can't return data nor decode it . Here is my code:
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
extension URL {
func withQueries(_ queries: [String: String]) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.queryItems = queries.flatMap { URLQueryItem(name: $0.0, value: $0.1) }
return components?.url
}
}
struct StoreItems: Codable {
let results: [StoreItem]
}
struct StoreItem: Codable {
var name: String
var artist: String
var kind: String
var artworkURL: URL
var description: String
enum CodingKeys: String, CodingKey {
case name = "trackName"
case artist = "artistName"
case kind
case artworkURL
case description
}
enum AdditionalKeys: String, CodingKey {
case longDescription
}
init(from decoder: Decoder) throws {
let valueContainer = try decoder.container(keyedBy: CodingKeys.self)
name = try valueContainer.decode(String.self, forKey: CodingKeys.name)
artist = try valueContainer.decode(String.self, forKey: CodingKeys.artist)
kind = try valueContainer.decode(String.self, forKey: CodingKeys.kind)
artworkURL = try valueContainer.decode(URL.self, forKey: CodingKeys.artworkURL)
if let description = try? valueContainer.decode(String.self, forKey: CodingKeys.description) {
self.description = description
} else {
let additionalValues = try decoder.container(keyedBy: AdditionalKeys.self)
description = (try? additionalValues.decode(String.self, forKey: AdditionalKeys.longDescription)) ?? ""
}
}
}
func fetchItems(matching query: [String: String], completion: @escaping ([StoreItem]?) -> Void) {
let baseURL = URL(string: "https://www.itunes.apple.com/search?")!
guard let url = baseURL.withQueries(query) else {
completion(nil)
print("Unable to build URL with supplied queries.")
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
let decoder = JSONDecoder()
if let data = data,
let storeItems = try? decoder.decode(StoreItems.self, from: data) {
completion(storeItems.results)
} else {
print("Either no data was returned or data was not properly decoded.")
completion(nil)
return
}
}
task.resume()
}
let query: [String: String] = [
"term": "Inside Out 2015",
"media": "movie",
"lang": "en_us",
"limit": "10"
]
fetchItems(matching: query) { (items) in
print(items)
}
And here is what's printed to the console which I guess shows that something is wrong with my "task":
Either no data was returned or data was not properly decoded.
nil
A couple of issues:
www
.artworkURL
appears to be optional, as your search didn't return a value for that key.When I fixed those, it works:
extension URL {
func withQueries(_ queries: [String: String]) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.queryItems = queries.flatMap { URLQueryItem(name: $0.0, value: $0.1) }
return components?.url
}
}
struct StoreItems: Codable {
let results: [StoreItem]
}
struct StoreItem: Codable {
var name: String
var artist: String
var kind: String
var artworkURL: URL?
var shortDescription: String?
var longDescription: String?
enum CodingKeys: String, CodingKey {
case name = "trackName"
case artist = "artistName"
case kind, artworkURL, shortDescription, longDescription
}
}
enum FetchError: Error {
case urlError
case unknownNetworkError
}
func fetchItems(matching query: [String: String], completion: @escaping ([StoreItem]?, Error?) -> Void) {
let baseURL = URL(string: "https://itunes.apple.com/search")!
guard let url = baseURL.withQueries(query) else {
completion(nil, FetchError.urlError)
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
completion(nil, error ?? FetchError.unknownNetworkError)
return
}
do {
let storeItems = try JSONDecoder().decode(StoreItems.self, from: data)
completion(storeItems.results, nil)
} catch let parseError {
completion(nil, parseError)
}
}
task.resume()
}
And:
let query = [
"term": "Inside Out 2015",
"media": "movie",
"lang": "en_us",
"limit": "10"
]
fetchItems(matching: query) { items, error in
guard let items = items, error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
Note, I'd suggest you add the error to the completion handler so that you can see why it failed (in your case, the first issue was that the URL was wrong).