0

I am using Swift to get a JSON from a Coronavirus API. However when I try to run the code I get this error.

Fatal error: Unexpectedly found nil while unwrapping an Optional value: line 22

My part of my code is

override func viewDidLoad() {
        super.viewDidLoad()
        
        let url = "https://api.coronavirus.data.gov.uk/v1/data?filters=areaType=nation;areaName=england&structure={%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}"
        getData(from: url)
        // Do any additional setup after loading the view.
    }
    
    private func getData(from url: String) {
        
        let getfromurl = URLSession.shared.dataTask(with: URL(string: url)!, 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.localizedDescription)")
            }
            
            guard let json = result else {
                return
            }
            
            print(json.data.date)
        })
        getfromurl.resume()
    
    }

Line 22 is:

let getfromurl = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: {data, response, error in

I am confused because I think that it means that url has nothing assigned to it, but even the debugger thinks it does.

UPDATE:

I can get the data but I get an error once I get it. The error 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))

The failed to convert shows it is an error in decoding the JSON and the values.

Leo Gaunt
  • 711
  • 1
  • 8
  • 29
  • Please do not modify the question all day long, because future readers will get confused when the answers refer to something that is not asked any more. Either add and "update" section or - if you ask something completely different(TM) - create a new question – Andreas Oetjen Feb 03 '21 at 09:16

3 Answers3

5

The exception does not mean that url is nil, but URL(string:url) is nil

You need to check if the url string is a valid url:

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
       /* ... */
    }
}

Update

Since the url string is now given: The problem are the curly braces; they are marked as unsafe in RFC1738 and should be replaced by %7b (opening) and %7d (closing), hence:

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"

let theUrl = URL(string: url)
print (theUrl)

This is also an example in the official API documentation:

curl -si 'https://api.coronavirus.data.gov.uk/v1/data?filters=areaType=nation;areaName=england&structure=%7B%22name%22:%22areaName%22%7D'
Community
  • 1
  • 1
Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34
  • 1
    So it does print oops so that really helps, but when I enter the URL into my browser it takes me to the page fine? – Leo Gaunt Feb 02 '21 at 21:36
  • What exactly is the failing url string? – Andreas Oetjen Feb 02 '21 at 22:53
  • it looks like "structure" parameter is incorrectly defined. it returns "Invalid structure" error. – musakokcen Feb 03 '21 at 06:35
  • Well, I'm getting data (at least in a browser); nevertheless, I'm not going to analyse what @LeoGaunt intended to query, I just tried to correct the syntax in order to create a valid `URL` instance. – Andreas Oetjen Feb 03 '21 at 07:14
  • @AndreasOetjen That Works! The URL is now valid. However it cannot get the data which is strange because I get an error saying it is in an incorrect format even though it is JSON, could you help me at all with that? – Leo Gaunt Feb 03 '21 at 08:21
  • I think this may be to do with what I am requesting. – Leo Gaunt Feb 03 '21 at 08:22
  • 1
    You should mark the answer as "accepted" and create a new question with details of what you are expecting and so on. I can take a look at that. – Andreas Oetjen Feb 03 '21 at 08:23
  • Sorry, I thought I already had, will do now – Leo Gaunt Feb 03 '21 at 08:27
2

The URL is not encoded properly. A browser has its own way to encode an URL but URL(string: does not do any encoding at all.

For this kind of complex URL it's recommended to create it with URLComponents/URLQueryItem. The benefit of URLComponents is it handles the encoding on your behalf

let structure = """
{"date":"date","areaName":"areaName","areaCode":"areaCode","newCasesByPublishDate":"newCasesByPublishDate","cumCasesByPublishDate":"cumCasesByPublishDate","newDeathsByDeathDate":"newDeathsByDeathDate","cumDeathsByDeathDate":"cumDeathsByDeathDate"}
"""

var components = URLComponents(string: "https://api.coronavirus.data.gov.uk")!
components.path = "/v1/data"
components.queryItems = [URLQueryItem(name: "filters", value: "areaType=nation;areaName=england"),
                         URLQueryItem(name: "structure", value: structure)]

if let url = components.url {
    print(url)
}

Side note:

Never print error.localizedDescription in a JSONDecoder catch block. It shows you only a meaningless generic error message. Always print(error)

vadian
  • 274,689
  • 30
  • 353
  • 361
0

made changes in getData method , please try this once

private func getData(from url: String) {
    
    guard let validUrl  = URL(string: url) else {
        return
    }
    let getfromurl = URLSession.shared.dataTask(with: validUrl, 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.localizedDescription)")
        }
        
        guard let json = result else {
            return
        }
        
        print(json.data.date)
    })
    getfromurl.resume()

}
Jagveer Singh
  • 2,258
  • 19
  • 34