SwiftUI: API URL fetch failed

Viewed 632

I'm trying to retrieve data from an API url This is what the implementation guide reads, so the URL should match this format:

The request for information in JSON format is submitted as a GET operation to the endpoint: http://digit-eyes.com/gtin/v2_0/?upc_code=x&app_key=x&signature=x&language=x&field_names=x

This is my function that fetches the data from the JSON and decodes it from JSON. I've replaced the signature and API Key with x. Signature is generated by combining the app_key and the barcode forming a hashed value.

func loadData() {
    guard let url = URL(string: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=x&signature=x&language=en&field_names=description,brand,ingredients,image,upc_code") else {
        print("Invalid URL")
        return
    }
    let request = URLRequest(url: url)
    URLSession.shared.dataTask(with: request) { data, responce, error in
        if let data = data {
            if let decodedRepsonce = try? JSONDecoder().decode(Response.self, from: data) {
                DispatchQueue.main.async{
                    self.results = decodedRepsonce.results
                }
                return
            }
        }
        print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")") //This is the error I get
    }.resume()
}

Here's what I get when I paste the URL into safari I've tested the URL with: "https://itunes.apple.com/search?term=radiohead&entity=song" and it works. A noticeable difference is that this link downloads a JSON file, my URL doesn't.

I store the JSON into an array Results:

struct Result: Codable {
    var description: String
    var brand: String
    var ingredients: String
    var image: String
    var upc_code: Int
}

Which is then displayed in the body:

var body: some View {
    GeometryReader { geometry in
        VStack(spacing: 0) {
        self.indicator.padding()
        List(self.results, id: \.upc_code) { item in
            VStack(alignment: .leading) {
                Text(item.brand)
                     .font(.headline)
                Text(item.description)
            }
        }
        
  }

EDIT

Dealing with nulls from the JSON data

To call loadData, I have an .onAppear on a VStack in the body.

    .onAppear {
            //let signiture = self.scannedCode.barcode.hashedValue("Ls75O8z1q9Ep9Kz0")
            self.loadData(url: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=/9nOS+obsRF5&signature=DiKl4lURenoNe53I0a/i3kiAkQQ=&language=en&field_names=description,ingredients,brand,image") { error, result  in
                if let err = error {
                    print(err)
                }
            }
    }
}

This is in a struct outside of the body

    func loadData(url: String, completion: @escaping (Error?, Result?) -> Void) {
        if let url = URL(string: url) {
            let task = URLSession.shared.dataTask(with: url) { data, response, error in
                guard let data = data, error == nil else {return}
                
                do {
                    let decoder = JSONDecoder()
                    let result: Result = try decoder.decode(Result.self, from: data)
                    completion(nil, result)
                }
                catch let e {
                    print(e)
                    completion(e, nil)
                }
            }

            task.resume()
        }
    }
}

I'm now getting:

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

In the JSON object, the brand name isn't always found, so it's sometimes null. I don't know how I can resume the decoder if a null is found.

1 Answers

Try this code to call in the body:

func loadData(url: String, completion: @escaping (Error?, Result?) -> Void) {
    if let url = URL(string: url) {
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {return}
            
            do {
                let decoder = JSONDecoder()
                let result: Result = try decoder.decode(Result.self, from: data)
                completion(nil, result)
            }
            catch let e {
                print(e)
                completion(e, nil)
            }
        }

        task.resume()
    }
}

loadData(url: "https://google.com") { error, result  in
    if let err = error {
        print(err)
    }
}

Try to modify the struct as follows and add the other variables. I also noticed that upc_code is a String.

struct Result: Codable {
    var description: String?
    var brand: String?
    var ingredients: String?
    var image: String?
    var upc_code: String?
    var return_message: String?
    var return_code: String?
}
Related