-1

When I do an API call and get data back it cannot be decoded into my struct properly.

The error I get is:

failed to convert valueNotFound(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "newDeathsByDeathDate", intValue: nil)], debugDescription: "Expected Int value but found null instead.", underlyingError: nil))

My Code is:

override func viewDidLoad() {
        super.viewDidLoad()
        
        let url = """
https://api.coronavirus.data.gov.uk/v1/data?filters=areaType=nation;areaName=england&structure=%7b%22date%22:%22date%22,%22areaName%22:%22areaName%22,%22areaCode%22:%22areaCode%22,%22newCasesByPublishDate%22:%22newCasesByPublishDate%22,%22cumCasesByPublishDate%22:%22cumCasesByPublishDate%22,%22newDeathsByDeathDate%22:%22newDeathsByDeathDate%22,%22cumDeathsByDeathDate%22:%22cumDeathsByDeathDate%22%7d
"""
        print(url)
        print(NSURL(string: url))
        getData(from: url)
        
        // Do any additional setup after loading the view.
    }
    
    private func getData(from url: String) {
        guard let theURL = URL(string: url) else { print ("oops"); return }
        let getfromurl = URLSession.shared.dataTask(with: theURL, completionHandler: {data, response, error in
            guard let data = data, error == nil else{
                print("Something Went Wrong")
                return
            }
            
            //Have data
            var result: Response?
            do {
                result = try JSONDecoder().decode(Response.self, from: data)
            }
            catch{
                print("failed to convert \(error)")
            }
            
            guard let json = result else {
                return
            }
            
            print(json.data)
        })
        getfromurl.resume()
    
    }
    
    
}

My Structs are:

struct Response: Codable {
    let data: [MyData]
}

struct MyData: Codable{
    let date: String
    let areaName: String
    let areaCode: String
    let newCasesByPublishDate: Int
    let cumCasesByPublishDate: Int
    let newDeathsByDeathDate: Int
    let cumDeathsByDeathDate: Int
}
Leo Gaunt
  • 711
  • 1
  • 8
  • 29
  • clearly in your struct `newDeathsByDeathDate` is an `Int` not `Int?` so while decoding the json it expected the Int value for key `newDeathsByDeathDate` but it found nil and because your declaration said it needs to have an Int value it failed. I am not sure how much of the response matches your struct, are u entirely sure all keys in json appropriately matches keys in struct? Is this the actual hierarchy ? and are u sure it needs to return Int and not Int? or String or double or anything else? if you need custom mapping you might need to override `init(from decoder:` – Sandeep Bhandari Feb 03 '21 at 09:27

1 Answers1

3

Please look closely at the JSON, the values for keys newDeathsByDeathDate and cumDeathsByDeathDate can be null.

Make the type of the corresponding struct members optional

struct MyData: Codable{
    let date: String
    let areaName: String
    let areaCode: String
    let newCasesByPublishDate: Int
    let cumCasesByPublishDate: Int?
    let newDeathsByDeathDate: Int?
    let cumDeathsByDeathDate: Int?
}

Alternatively – to keep non-optional values – decode the JSON manually and set the values to 0 if they are null


struct MyData : Decodable {
    let date, areaName, areaCode: String
    let newCasesByPublishDate, cumCasesByPublishDate : Int
    let newDeathsByDeathDate, cumDeathsByDeathDate: Int
    
    private enum CodingKeys : String, CodingKey { case date, areaName, areaCode, newCasesByPublishDate, cumCasesByPublishDate, newDeathsByDeathDate, cumDeathsByDeathDate }
    
    init(from decoder : Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        date = try container.decode(String.self, forKey: .date)
        areaName = try container.decode(String.self, forKey: .areaName)
        areaCode = try container.decode(String.self, forKey: .areaCode)
        newCasesByPublishDate = (try? container.decode(Int.self, forKey: .newCasesByPublishDate)) ?? 0
        cumCasesByPublishDate = (try? container.decode(Int.self, forKey: .cumCasesByPublishDate)) ?? 0
        newDeathsByDeathDate = (try? container.decode(Int.self, forKey: .newDeathsByDeathDate)) ?? 0
        cumDeathsByDeathDate = (try? container.decode(Int.self, forKey: .cumDeathsByDeathDate)) ?? 0
    }
}

And again, don't ignore useful error messages by printing something meaningless

Replace

print("Something Went Wrong")

with

print("An error occured:", error!)
vadian
  • 274,689
  • 30
  • 353
  • 361