SwiftUI: API URL fetch failed - ios

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.

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

Swift - How do I show Data in Swift View from Data.swift file?

In a separate data file called data.swift I have this code
struct Response: Decodable {
var data: Data
}
struct Data: Decodable {
var search: search
}
struct search: Decodable {
var __Typename: String
var query: String
var searchResults: searchResults
}
...and so on and so forth. I then decode the data from a Rapid-Api like so
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error!)
} else {
let products = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
if let water = products {
print("JSON: \n" + String(describing: water) + "\n")
}
}
})
How do I display the data elements in ProductList.swift it's a (view) file. The API works as expected and displays the JSON in the terminal. I am using XCODE 12.4 as I am not permitted to upgrade any further.
So, actually you want to receive the data in Model and show in view, either a label or image.
In your productSwift.List:
var response: Response?
Right now, you have to decode the data in the Model:
static func postApiCall<T: Decodable>(completion: #escaping (Result<T,Error>) -> Void) {
let url = URL(string: "Enter Your URL here")
let request = URLRequest(url: url!)
let dataTask = URLSession.shared.dataTask(with: request) { data, response , error in
guard let data = data else {
if error == nil {
completion(.failure(error as! Error))
}
return
}
do {
let decoder = JSONDecoder()
let json = try decoder.decode(T.self, from: data)
completion(.success(json))
} catch let error {
print(error.localizedDescription)
}
}
dataTask.resume()
}
}
Now in your ProductList.swift:
ServiceManage.postApiCall { (result : Result<Response,Error>) in
switch result {
case .success(let result):
print("result is \(result)")
self.response = response.data
self.yourLabel.text = response.data.search.query
case .failure(let failure):
print(failure)
}
}
and as Larme said, change your Struct "Data" name to something else.

JSON Decode Function From File Versus Web

SwiftUI is supposed to simplify things- I am bit frustrated as I have been working on the URLSession+JSONDecoder for weeks, I really need some help!
I have a function to load JSON data from a file in Swift and it works as expected. I copy/pasted the function and updated it to get the data via an API, however I receive a compile time error: "Unexpected non-void return value in void function". Is my approach wrong to use a function for JSON over the web?
JSON response:
{
"T":"CSU",
"v":468303,
"vw":1.2838,
"o":1.31,
"c":1.24,
"h":1.38,
"l":1.2001,
"t":1607374800000,
"n":994
}
struct Root2: Codable {
var T: String
var v: Double
var vw: Double
var o: String
var c: String
var h: Double
var l: Double
var t: Double
}
This file-based function works as expected:
let symbolData: [Root2] = load("symbolData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
For the web version, I receive compile time error: "Unexpected non-void return value in void function".
Line: return try decoder.decode(T.self, from: data)
func loadURL<T: Decodable>() -> T {
guard let url = URL(string: """)
else {
fatalError("Invalid URL in main bundle.")
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
do {
if let data = data {
let stringData = String(decoding: data, as: UTF8.self)
print("1 Fetched: \(url)")
print("2 Response: \(stringData)")
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
}
}
catch {
fatalError("Couldn't parse as :\n\(error)")
}
}.resume()
}
Working version after Leo's help!
class Manager: ObservableObject {
#Published var symbols: [Symbol] = []
func loadURL<T: Decodable>(using decoder: JSONDecoder = .msSince1970, completion: #escaping ((T?, Error?) -> Void)) {
let url = URL(string: """)!
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("ops")
completion(nil, error)
return
}
print("1 Fetched: \(url)")
print("2 Response:", String(data: data, encoding: .utf8) ?? "")
_ = Data("""
[
{
"open": {
"price": 124.02,
"time": 1657105851499
},
"close": {
"price": 124.96,
"time": 1618647822184
},
"high": 124.64,
"low": 124.65,
"volume": 75665274,
"symbol": "AAPL"
}
]
""".utf8)
do {
completion(try decoder.decode(T.self, from: data), nil)
//completion(try decoder.decode(T.self, from: tempDataForTesting), nil)
} catch {
completion(nil, error)
}
}.resume()
}
}
extension JSONDecoder {
static let msSince1970: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
return decoder
}()
}
You can't wait for an asynchronous task to finish and return the result. What you need is a completion handler. You would need also to explicitly set the resulting type if you don't pass the resulting type to your decode method and you need to call resume to start your url session data task:
import SwiftUI
struct ContentView: View {
#ObservedObject var manager = Manager()
#State var string: String = "Hello, world!"
var body: some View {
Text(manager.symbol)
.padding()
.onAppear {
manager.load(symbol: manager.symbol) { (symbols: [Symbol]?, error: Error?) in
guard let symbols = symbols else {
print("error:", error ?? "")
string = "JSON could not be parsed"
return
}
for symbol in symbols {
print(symbol.open.price)
print(symbol.open.time)
print(symbol.close.price)
print(symbol.close.time)
print(symbol.high)
print(symbol.low)
print(symbol.volume)
print(symbol.symbol)
DispatchQueue.main.async {
manager.symbols = symbols
}
}
string = "JSON was successufly parsed"
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class Manager: ObservableObject {
#Published var symbols: [Symbol] = []
#Published var symbol: String = "IBM"
func load<T: Decodable>(symbol: String, using decoder: JSONDecoder = .msSince1970, completion: #escaping ((T?, Error?) -> Void)) {
guard let url = URLComponents(symbol: symbol).url else {
completion(nil, URL.Error.invalidURL)
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("ops")
completion(nil, error)
return
}
print("1 Fetched: \(url)")
print("2 Symbol: \(symbol)")
print("3 Response:", String(data: data, encoding: .utf8) ?? "")
do {
completion(try decoder.decode(T.self, from: data), nil)
} catch {
completion(nil, error)
}
}.resume()
}
}
struct Symbol: Codable {
let open, close: Price
let high, low: Double
let volume: Int
let symbol: String
}
struct Price: Codable {
let price: Double
let time: Date
}
extension JSONDecoder {
static let msSince1970: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
return decoder
}()
}
extension URLComponents {
init(scheme: String = "https",
host: String = "sandbox.iexapis.com",
path: String = "/stable/stock/market/ohlc",
symbol: String,
token: String = "YOUR_API_TOKEN") {
self.init()
self.scheme = scheme
self.host = host
self.path = path
self.queryItems = [URLQueryItem(name: "symbols", value: symbol),
URLQueryItem(name: "token", value: token)]
}
}
extension URL {
enum Error: String, Swift.Error {
case invalidURL = "Invalid URL"
}
}
This will print
1 Fetched: https://sandbox.iexapis.com/stable/stock/market/ohlc?symbols=IBM&token=YOUR_API_TOKEN
2 Symbol: IBM
3 Response: [{"open":{"price":128.9,"time":1636600302693},"close":{"price":131.44,"time":1662259300134},"high":132.517,"low":130.074,"volume":3403359,"symbol":"IBM"}]
128.9
2021-11-11 03:11:42 +0000
131.44
2022-09-04 02:41:40 +0000
132.517
130.074
3403359
IBM

Getting objects from JSON

My problem:
I use the site API - https://www.themealdb.com/api.php .
I want to get a list of all products. For this purpose, the link is https://www.themealdb.com/api/json/v1/1/categories.php
In my code, I created a structure:
struct Category: Decodable {
var idCategory: Int?
var strCategory: String?
var strCategoryDescription: String?
var strCategoryThumb: String?
}
Then I try to get to the address and get the data. I can convert the incoming data to JSON. It works.
Next, I want to convert the data and write it into an array of structures.
func load(url: String, completion: #escaping (_ objects: [Category])->()) {
guard let url = URL(string: url) else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
//let json = try? JSONSerialization.jsonObject(with: data, options: [])
//print("JSONSerialization" + "\(json)")
let object = try JSONDecoder().decode([Category].self, from: data)
print("JSONDecoder" + "\(object)")
completion(object)
} catch {
print(error.localizedDescription)
}
}.resume()
}
But in this line I get an error in the console:
The data couldn’t be read because it isn’t in the correct format.
Probably a mistake in my structure. I can not deal with this problem.
There are two mistakes.
The actual error
Type 'Array' mismatch: Expected to decode Array but found a dictionary instead.
indicates that you are ignoring the root object, the dictionary with key categories
The value for key id is String not Int, note the double quotes in the JSON
Declare all struct members as non-optional constants as the JSON provides all keys the in dictionaries. And please map the horrible dictionary keys to more meaningful member names.
And print all errors and never .localizedDescription in a Decodable catch block.
struct Response: Decodable {
let categories: [Category]
}
struct Category: Decodable {
let id: String
let name: String
let description: String
let thumbnailURL: URL
private enum CodingKeys: String, CodingKey {
case id = "idCategory"
case name = "strCategory"
case description = "strCategoryDescription"
case thumbnailURL = "strCategoryThumb"
}
}
func load(url: String, completion: #escaping ([Category]) -> Void) {
guard let url = URL(string: url) else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
do {
let response = try JSONDecoder().decode(Response.self, from: data!)
print("JSONDecoder", response)
completion(response.categories)
} catch {
print(error)
completion([])
}
}.resume()
}
You need two codables
struct MyData: Codable {
var categories: [Category]?
}
And
let object = try JSONDecoder().decode(MyData.self, from: data)
With a wrapper class you can fetch your categories. The following code works fine in Playground:
let json = """
{
"categories": [
{"idCategory": "1"},
{"idCategory": "2"}
]
}
"""
struct CategoryHolder: Codable {
var categories: [Category]
}
struct Category: Codable {
let idCategory: String?
let strCategory: String?
let strCategoryDescription: String?
let strCategoryThumb: String?
}
let jsonData = Data(json.utf8)
let categories = try JSONDecoder().decode(CategoryHolder.self, from: jsonData).categories

Parsing JSON response using Codable gives error in swift

I'm trying to parse JSON response using Codable but it gives me error.
I tried to refer from the below stack overflow link but did not work.
how do I parse Any in dictionary using swift
Below is my code, not sure where I'm wrong in this.
> enum JSONError: String,Error {
> case NoData = "ERROR: no data"
> case ConversionFailed = "ERROR: conversion from JSON failed"
> }
struct Owner : Decodable {
let full_name : String
let html_url:String
let follower:follower
}
struct follower : Decodable {
let followers_url : String
}
func jsonParser() {
let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(url:endpoint as URL)
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
guard let data = data else {
throw JSONError.NoData
}
let jsonResponse = try JSONSerialization.jsonObject(with:
data)
let entries = try! JSONDecoder().decode([Owner].self, from: jsonResponse as! Data)
print(jsonResponse)
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
I need to get 3 information from this response - full name, html url and followers.
Link for web api
https://api.github.com/search/repositories?q=language:ruby
Please latest have a look at the code.
Below is the error message :
'__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580). 2019-02-09
16:17:42.062971+0530 PhotoViewwer[13342:259997] Could not cast value
of type '__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580).
Thanks
Please learn to read JSON. It's pretty easy. There are only two collection types, array ([]) and dictionary ({})
Your structs are wrong.
In the root dictionary of the JSON there is an array of dictionaries for key items.
In each dictionary there are keys full_name and owner (here is the location of the Owner struct).
A dictionary for key follower does not exist.
These structs represent the JSON correctly
struct Response : Decodable {
let items : [Item]
}
struct Item : Decodable {
let fullName : String
let owner : Owner
}
struct Owner : Decodable {
let htmlUrl : URL // URL strings can be decoded directly into URL
let followersUrl : URL
}
Add a completion handler to your function and use an enum as result type. The failure case returns all real errors. An URLRequest is redundant. Just pass the URL. The JSONSerialization line is pointless.
The convertFromSnakeCase strategy converts snake_cased keys to camelCased struct members
enum Result {
case success(Response), failure(Error)
}
func jsonParser(completion: #escaping (Result) -> Void) {
let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let entries = try decoder.decode(Response.self, from: data!)
completion(.success(entries))
} catch {
completion(.failure(error))
}
}.resume()
}
And call it
jsonParser { result in
switch result {
case .success(let entries) : print(entries)
case .failure(let error) : print(error)
}
}
Basically never use NS... classes if there are native equivalents, here URL for NSURL and URLRequest for NS(Mutable)URLRequest
Edit:
In Swift 5 the syntax becomes more convenient using the native Result type. It is able to convert the throwing expression
enum Result {
case success(Response), failure(Error)
}
func jsonParser(completion: #escaping (Result<Response,Error>) -> Void) {
let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
completion(Result{ try decoder.decode(Response.self, from: data!) })
}.resume()
}
You need
func jsonParser() {
let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(url:endpoint as URL)
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let entries = try dec.decode(Root.self, from:data!)
print(entries)
} catch {
print(error)
}
}.resume()
}
struct Root : Decodable {
let items:[Item]
}
struct Owner: Codable {
let login: String
let id: Int
let nodeId: String
let avatarUrl: String
let gravatarId: String
let url, htmlUrl, followersUrl: String
let followingUrl, gistsUrl, starredUrl: String
let subscriptionsUrl, organizationsUrl, reposUrl: String
let eventsUrl: String
}
struct Item: Codable {
let fullName : String
let htmlUrl:String
let owner: Owner
}
You shouldn't cast the response to data here
from: jsonResponse as! Data)
as it will crash

Swift 4 adding parameters to URLRequest completion handler

I'm trying to return some data from a URLRequest in Swift 4, and to do so I've added a completion handler to my function signature, just with a Bool for the time being. This is the function:
func getJson(completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data, err == nil else {
print(err!)
return
}
do {
let response = try
JSONDecoder().decode(TopStoriesResponse.self, from: data)
print(response.results)
// Pass results into arrays (title, abstract, url, image)
completionHandler(true)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
and I call it in viewDidLoad like so:
getJson { (success) in
print("Success")
}
Nothing is printing to the console so I'm wondering if I am using the completion handler correctly? But ultimately I'd like to switch out the Bool and instead pass a few values out of the request, back into arrays that I have in my view controller.
These are the structs I'm using to grab the pieces of JSON that i want:
struct TopStoriesResponse: Decodable {
let status: String
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
}
And I'm ultimately trying to fill these arrays in my view controller with the parsed JSON so I can arrange them in a table view:
var headlines = [String]()
var abstracts = [String]()
var urls = [URL]()
EDIT: Full code in case I'm going wrong somewhere else: https://pastebin.com/r402GKej
try creating the struct TopStoriesResponse and Story seperately from the ViewController and add the Networking struct to load data from the API
struct TopStoriesResponse: Decodable {
let status: String
let copyright: String
let num_results: Int
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
}
struct Networking {
static func getJson(completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808"
guard let url = URL(string: jsonUrlString) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data, error == nil else {
print(error!.localizedDescription)
return
}
do {
let response: TopStoriesResponse = try JSONDecoder().decode(TopStoriesResponse.self, from: data)
print(response.results.count)
completionHandler(true)
} catch {
print(error.localizedDescription)
completionHandler(false)
}
}.resume()
}
}
Now try calling Networking.getJson from the ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
Networking.getJson { (result) in
print(result)
}
}
}

Resources