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

Answered by OOPer in 658688022
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.)
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