-1

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!

Zemelware
  • 93
  • 7
  • The URL in she code and the URL in the text are different. The JSON of the former is not an array. – vadian Aug 12 '20 at 04:50
  • @vadian well I feel like a complete idiot, I forgot to change the URL! But I changed it and it didn't fix the issue. (I will update the URL in the code and the error message) – Zemelware Aug 12 '20 at 05:00
  • The `url` that you have mentioned here returns an `Object` and it is completely different from the `model` you have created. – caldera.sac Aug 12 '20 at 05:00

1 Answers1

0

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
}
Sauvik Dolui
  • 5,278
  • 3
  • 32
  • 39
  • I really appreciate the answer, that fixed it! But I'm confused why those two properties need to be optionals. They are given a value in the JSON file, so why would they need to be optionals and not anything else? I am quite new with optionals, so sorry if this sounds very obvious. – Zemelware Aug 12 '20 at 05:12
  • `why those two properties need to be optionals?` Since incoming json response does to confirm to always give you a value for `recovered` and `active`, we should mark them as optionals, else error is thrown. `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))` i.e. index 10, value for `recovered` field is `nil`, while it was expecting an `Int`. – Sauvik Dolui Aug 12 '20 at 05:31
  • Okay that makes more sense. By any chance do you know how to return the json constant? I can't have a return statement inside the closure, but if I put it outside then I can't reference the json constant and return it at all. – Zemelware Aug 12 '20 at 05:44
  • I think you might be looking for something like a Completion Closure. – Tagnal Aug 12 '20 at 06:30
  • This is an async call, ideally you should pass one completion block to `func getData(from url: String)`, to handle the result/error. This answer might be helpful https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function#answer-54597264. – Sauvik Dolui Aug 12 '20 at 06:36
  • @SauvikDolui so I implemented the async call, but the issue I'm having is that I can access my data in the closure that's passed as a parameter into the function, but not outside of the closure. To see what I mean, look at my code as I've updated it and it'll make more sense. Thank you. – Zemelware Aug 12 '20 at 15:36
  • `print(coronaInfo[0].cases) // – Sauvik Dolui Aug 12 '20 at 15:51
  • @SauvikDolui I'm a little confused. I'm accessing the data after the getData() function call so shouldn't I be able to access the data since it's after the API call returns the data? What if I want to use the data outside of the closure? – Zemelware Aug 12 '20 at 21:21