Search code examples
jsonswiftjsondecoder

How do I parse JSON file with an array of objects in Swift?


For several hours I've been trying to parse to a JSON file with an array to experiment with API's, but I keep getting errors. I managed to parse a file with just one JSON object/dictionary (following different tutorials), but I can't figure it out for a JSON file with an array of objects. I'm testing this out with a coronavirus API. Here is the URL so you can see the JSON code: https://coronavirus-19-api.herokuapp.com/countries. I feel like the solution is very simple and there's just something small that I'm missing, but I'm not really sure.

This is the error I get:

valueNotFound(Swift.Int, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 10", intValue: 10), CodingKeys(stringValue: "recovered", intValue: nil)], debugDescription: "Expected Int value but found null instead.", underlyingError: nil))

Here is my code:

import UIKit

class FirstViewController: UIViewController {
    
    var coronaInfo = [CoronaInfo]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let url = "https://coronavirus-19-api.herokuapp.com/countries"
        
        getData(from: url) { (data) in
            self.coronaInfo = data
            print(self.coronaInfo[0].cases)   // <-- Works in here
        }
        
        print(coronaInfo[0].cases) // <-- Outside of the closure it's nil
    }
    
    func getData(from url: String, completed: @escaping ([CoronaInfo]) -> ()) {
        URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
            // Make sure that data isn't nil and that there was no error
            guard let data = data, error == nil else { print("Something went wrong"); return }
            
            var result: [CoronaInfo]?
            do {
                // Convert data from bytes to the custom object (Known as JSON decoding)
                result = try JSONDecoder().decode([CoronaInfo].self, from: data)
                
                guard let json = result else { return }
                
                DispatchQueue.main.async {
                    completed(json)
                }
            } catch { print(error) }
            
        }.resume()
    }

}

struct CoronaInfo: Codable {
    let country: String
    let cases: Int
    let todayCases: Int
    let deaths: Int
    let todayDeaths: Int
    let recovered: Int?
    let active: Int?
    let critical: Int
    let casesPerOneMillion: Int
    let deathsPerOneMillion: Int
    let totalTests: Int
    let testsPerOneMillion: Int
}

Thanks in advance for any help!


Solution

  • Two points worth mentioning here

    1. The response you are trying to parse should come from https://coronavirus-19-api.herokuapp.com/countries not from https://corona-virus-stats.herokuapp.com/api/v1/cases/general-stats. So use first link instead of second.
            let url = "https://coronavirus-19-api.herokuapp.com/countries"
            getData(from: url)
    
    1. Since there are two fields with null values, mark them optional in your model. enter image description here
    struct CoronaData: Codable {
        let country: String
        let cases: Int
        let todayCases: Int
        let deaths: Int
        let todayDeaths: Int
        let recovered: Int?
        let active: Int?
        let critical: Int
        let casesPerOneMillion: Int
        let deathsPerOneMillion: Int
        let totalTests: Int
        let testsPerOneMillion: Int
    }