Generic JSON response from API request

Hello guys. I've been stuck two days with this issue, hope you can understand my question below because I'm still new with Swift.

I want to create a generic function that request some JSON data from an API, decode the response using the Codable protocol and save it in a @State variable to update the View

Here is what I've done so far.
Code Block swift
struct Response: Codable {
    var results: [Result]
}
struct Result: Codable {
    var trackId: Int
    var trackName: String
    var collectionName: String
}

Here is the function
Code Block swift
    func loadData<T: Decodable>(model: T.Type) {
        guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song") else {
            print("Invalid URL")
            return
        }
        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let decodedResponse = try? JSONDecoder().decode(model, from: data) {
                    DispatchQueue.main.async {
                        print(decodedResponse)
                    }
                    return
                }
            }
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
        }
        .resume()
    }

The thing is, so far I'm only able to print the response, but I can't do something like this:
Code Block swift
DispatchQueue.main.async {
self.results = decodedResponse.results
}

Because it shows me this error Value of type 'T' has no member ' results'

Here is the View
Code Block swift
struct TestView: View {
    @State private var results = [Result]()
    var body: some View {
        List(results, id: \.trackId) { item in
            VStack(alignment: .leading) {
                Text(item.trackName)
                    .font(.headline)
                Text(item.collectionName)
            }
        }
        .onAppear {
            loadData(model: Response.self)
        }
    }
// Here is the function defined
}

Somebody could give a hint? I really appreciate it

Thank you.
Dennis

Accepted Answer
If you want to make your loadData generic and access the property results of decodedResponse,
You need to tell that type T has a property named results.

For example:
Code Block
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
var trackId: Int
var trackName: String
var collectionName: String
}
protocol HoldingResults {
associatedtype R
var results: [R] {get}
}
extension Response: HoldingResults {}


As you see, the protocol HoldingResults is used to tell Swift that the type conforming to it has a property named results.
You can define your loadData using the protocol:
Code Block
func loadData<T: Decodable>(model: T.Type, completion: @escaping ([T.R])->Void)
where T: HoldingResults
{
guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let decodedResponse = try JSONDecoder().decode(model, from: data)
DispatchQueue.main.async {
print(decodedResponse)
completion(decodedResponse.results)
}
return
} catch {
print(error)
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}
.resume()
}

And use it as:
Code Block
loadData(model: Response.self) {
self.results = $0
}


(I replaced if-let-try? to do-try-catch, that's my preference and not required.)

Because it shows me this error Value of type 'T' has no member ' results'

That's true ! load() does not know anything yet about T.

Why do you want a generic here ? And not directly Response ?

Otherwise, you need to test is T is Response and cast it to Response.
But what's the interest in doing so ?


It works perfectly, thank you very much.
Generic JSON response from API request
 
 
Q