How to make GET API calls that has large JSON response in Swift 5?

Viewed 1126

I am trying to fetch the Covid 19 data of all countries and their states from disease.sh.

I have previously fetched json data from different APIs using this method. The response in those cases were shorter compared to this.

I have posted the codes below:

//  Webservice.swift

import Foundation

class Webservice {
    
    let countriesURL: String = "https://disease.sh/v3/covid-19/jhucsse"
    
    func getAllCountries(completion: @escaping ([Country]?) ->()) {
        
        guard let url = URL(string: countriesURL) else {
            
            completion(nil)
            return
        }
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            
            guard let data = data, error == nil else {
                
                print("No data in response: \(error?.localizedDescription ?? "Unknown error").")
                
                DispatchQueue.main.async {
                    completion(nil)
                }
                return
            }
            
            let countries = try? JSONDecoder().decode([Country].self, from: data)
            DispatchQueue.main.async {
                countries == nil ? completion(nil) : completion(countries)
            }
        }.resume()
        
    }
    
}

Since, I was using MVVM design, here are my Model, ViewModel and View.

//  Model
//  Country.swift

import Foundation

struct Country: Decodable {
    
    var country: String
    var updatedAt: String
    var stats: Stats
    var coordinates: Coordinates
    var province: String
}

struct Stats: Decodable {
    
    var confirmed: Int
    var deaths: Int
    var recovered: Int
}

struct Coordinates: Decodable {
    
    var latitude: String
    var longitude: String
}
//  ViewModel
//  CountryListViewModel.swift

import Foundation

class CountryListViewModel: ObservableObject {
    
    @Published var countries = [CountryViewModel]()
    
    init() {
        fetchCountries()
    }
    
    func fetchCountries() {
        Webservice().getAllCountries() { countries in
            if let countries = countries {
                self.countries = countries.map(CountryViewModel.init)
            }
        }
    }
    
}


class CountryViewModel {
    
    var country: Country
    
    init(country: Country) {
        
        self.country = country
    }
    
    let id = UUID()
    
    var name: String {
        return self.country.country
    }
    
    var updatedAt: String {
        return self.country.updatedAt
    }
    
    var stats: Stats {
        return self.country.stats
    }
    
    var coordinates: Coordinates {
        return self.country.coordinates
    }
    
    var province: String {
        return self.country.province
    }
    
}
//  View
//  ContentView.swift


import SwiftUI

struct ContentView: View {
    
    @ObservedObject private var  countryListVM = CountryListViewModel()
    
    
    var body: some View {
        List( self.countryListVM.countries, id:\.id) { country in
            Text(country.name)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

My issue is when I call the Webservice.getAllCountries() it returns nil. Could anyone look at the code and tell me what is wrong, please? Thank you!

PS: I created a mock json with fewer objects (20-30) and called Webservice.getAllCountries() in this case it returned and mapped the values. It is not working with larger JSON response. Help!!

1 Answers

You should avoid using try ? except in situations where you really don't care about failures. do/try/catch is a better approach since it will tell you why something failed.

Changing your code to

do {
    let countries = try JSONDecoder().decode([Country].self, from: data)
    DispatchQueue.main.async {
        completion(countries)
    }
} catch {
    print(error)
    completion(nil)
}

Gives us an error on the console -

Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 509", intValue: 509), CodingKeys(stringValue: "province", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))

which makes sense, since not all countries have provinces. To fix this, make province an optional in your data model

struct Country: Decodable {
    
    var country: String
    var updatedAt: String
    var stats: Stats
    var coordinates: Coordinates
    var province: String?
}
Related