JSONDecoder does not work for Swift code (SWIFT UI) - swift

I'm trying to decode data from JSONDecoder and API for my app.
fetchDate() in ContentVeiw.swift works, but decodedResponse is nil which means JSONDecoder needs to be fixed.
How do I fix this?
Here's my code.
ContentView.Swift
func fetchData() async {
let urlString = "http://carbonateapiprod.azurewebsites.net/api/v1/mealprovidingunits/\(restaurant.mealProvidingUnitID)/dishoccurrences?startDate=2022-04-04&endDate=2022-04-08"
print(urlString)
guard let url = URL(string: urlString) else {
print("Bad URL: \(urlString)")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Restaurants.self, from: data) {
restaurants = decodedResponse.restaurants
print("here") // doesn't work!
}
loadingState = .loaded
print("here!")
} catch {
loadingState = .failed
print("here?")
}
}
Restaurants.swift
import Foundation
struct Restaurants: Codable {
var restaurants: [Restaurant]
}
Restaurant.swift
import Foundation
// MARK: - Restaurant
struct Restaurant: Codable, Identifiable {
let id, startDate, endDate, dishID: String
let mealProvidingUnitID: String
let mealProvidingUnit: MealProvidingUnit
let displayNames: [DisplayName]
let dishTypeID: String
let dishType: DishType
let dish: Dish
let availableDishTypes: [DishType]
let editableByDefault: Bool ....
JSON data looks like this
[
{
"id": "1a3f866d-0670-4d06-9118-08da019145a7",
"startDate": "4/6/2022 12:00:00 AM",
"endDate": "4/6/2022 12:00:00 AM",
"dishID": "9cfcb36c-0ff9-4bf5-b4e5-db0b2a4f0894",
"mealProvidingUnitID": "21f31565-5c2b-4b47-d2a1-08d558129279",
"mealProvidingUnit": {
"id": "21f31565-5c2b-4b47-d2a1-08d558129279",
"mealProvidingUnitName": "Kårrestaurangen",
"organizationID": "11fbbb8c-14f0-44f4-7457-08d556947c13",
"showFoods": true,
"showArticles": true,
"longitude": 0.0,
"latitude": 0.0,
"sevenDayWeek": false,
"displayNameCategories": null,
"dishTypes": [
{
"id": "16ea0d5a-8082-4b8a-9003-08d621fbccd9",
"dishTypeName": "Classic Vegan",
"dishTypeNameEnglish": "Classic Vegan",
"isEnabled": true,
"price": 0.0,
"hasPrice": false
} ...

use this example code (works for me). Your Restaurants struct does not match the json data, there is no
restaurants field in the json data. However, [Restaurant] does fit the json data. See also my comment regarding https.
let urlString = "https://carbonateapiprod.azurewebsites.net/api/v1/mealprovidingunits/\(restaurant.mealProvidingUnitID)/dishoccurrences?startDate=2022-04-04&endDate=2022-04-08"
and
if let decodedResponse = try? JSONDecoder().decode([Restaurant].self, from: data) {
restaurants = decodedResponse
print("\n \(restaurants) \n")
}

Related

Variable Not Passing In POST parameter in Swift

I'm trying to get the cities by country using the POSTMAN Api. When I don't bring any variables into the parameter, the request works as expected. Though, when I try to use a global variable as a parameter, it returns empty. It works perfectly fine if it was coded as such: "country": "Nigeria" (everything else the same)
Code below:
let myCountry = selectedCountryString.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
guard let url = URL(string: "https://countriesnow.space/api/v0.1/countries/population/cities/filter") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"limit": 10,
"order": "dsc",
"orderBy": "value",
"country": "\(myCountry)"
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
let task = URLSession.shared.dataTask(with: request) {data, _, error in
guard let data = data, error == nil else {
return
}
do{
let response = try JSONDecoder().decode(CitiesPopModel.self, from: data)
onCompletion(response)
}
catch {
print("Error country -> \(myCountry)")
}
}
task.resume()
}
I switched my code to this and it is now working with the variable:
func callCitiesByPopAPI(completion: #escaping (CitiesPopModel?, Error?) -> ()) {
let url = "https://countriesnow.space/api/v0.1/countries/population/cities/filter"
let parameters: [String: Any] = [
"limit": 20,
"order": "dsc",
"orderBy": "value",
"country": selectedCountryString
]
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: CitiesPopModel.self) { response in
if let error = response.error {
completion(nil, error)
return
}
if let result = response.value {
completion(result, nil)
print("City pop model result is \(result)")
return
}
}
}
Not worry about it you have to pass selectedCountryString value as like it then all goes perfectly
let selectedCountryString = "INDIA"
Replace value forHttpHeaderField for this:
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
And replace httpBody for this:
request.httpBody = body.percentEncoded()
I tested this with Algeria, it's worked.
extension Dictionary {
func percentEncoded() -> Data? {
map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
.data(using: .utf8)
}
}
extension CharacterSet {
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed: CharacterSet = .urlQueryAllowed
allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return allowed
}()
}
Here is my test code that shows how to fetch the data from the server,
using a variable as a parameter (country) to the POST request, and display it in a view. Works very well for me.
struct ContentView: View {
#State var cityPop = CitiesPopModel()
var body: some View {
List(cityPop.data) { data in
VStack {
Text(data.city).foregroundColor(.blue)
ScrollView(.horizontal) {
HStack {
ForEach(data.populationCounts) { pop in
VStack {
Text(pop.year)
Text(pop.sex)
Text(pop.value).foregroundColor(.blue)
}
}
}
}
}
}
.onAppear {
getCountry(country: "Australia") { result in
if let popData = result {
cityPop = popData
}
}
}
}
func getCountry(country: String, completion: #escaping(CitiesPopModel?) -> Void) {
guard let url = URL(string: "https://countriesnow.space/api/v0.1/countries/population/cities/filter") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"limit": 10,
"order": "dsc",
"orderBy": "value",
"country": country
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else { return }
do {
let response = try JSONDecoder().decode(CitiesPopModel.self, from: data)
return completion(response)
}
catch {
print("Error country -> \(country)")
}
completion(nil)
}.resume()
}
}
// MARK: - CitiesPopModel
struct CitiesPopModel: Codable {
var error: Bool = false
var msg: String = ""
var data: [City] = []
}
// MARK: - City
struct City: Identifiable, Codable {
let id = UUID()
let city: String
let country: String
let populationCounts: [Population]
enum CodingKeys: String, CodingKey {
case city, country, populationCounts
}
}
// MARK: - Population
struct Population: Identifiable, Codable {
let id = UUID()
let year, value, sex, reliabilty: String
enum CodingKeys: String, CodingKey {
case year, value, sex, reliabilty
}
}

Parse Wiki Nearby

I'm new to Swift, and trying to figure out how to parse the JSON returned by Wiki Nearby. With the help of https://www.youtube.com/watch?v=sqo844saoC4 here's where I am right now:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = "https://en.wikipedia.org/w/api.php?action=query&list=geosearch&gscoord=37.7891838%7C-122.4033522&gsradius=10000&gslimit=2&format=json"
getData(from: url)
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: {data, response, error in
guard let data = data, error == nil else {
print("Something went wrong")
return
}
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(type(of: json.query.geosearch))
print(json.query.geosearch.self)
})
task.resume()
}
}
struct Response: Codable {
let batchcomplete: String
let query: Query
}
struct Query: Codable {
let geosearch: [Geosearch]?
}
struct Geosearch: Codable {
let pageid: Int?
let title: String?
}
Following Need help parsing wikipedia json api in SWIFT I believe that geosearch is a dictionary. The code runs, but how do I retrieve the title or pageid from geosearch (print(json.query.geosearch.title) gives an error)?
Sorry - probably something really basic... any pointers would be appreciated.
Philipp
try the following code. It uses a completion closure to "wait" until the results are fetched before
returning them. Since geosearch is an array you need to loop through it. Note, the json response is parsed without errors. Works for me.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = "https://en.wikipedia.org/w/api.php?action=query&list=geosearch&gscoord=37.7891838%7C-122.4033522&gsradius=10000&gslimit=2&format=json"
getData(from: url) { response in
if let result = response,
let geosearch = result.query.geosearch {
print("-----> result: \(result) \n")
for geo in geosearch {
print("geo.title: \(geo.title)")
}
}
}
}
func getData(from url: String, completion: #escaping(Response?) -> ()) {
if let theURL = URL(string: url) {
let task = URLSession.shared.dataTask(with: theURL) { data, response, error in
guard let data = data, error == nil else {
print("Something went wrong")
completion(nil)
return
}
do {
let result = try JSONDecoder().decode(Response.self, from: data)
completion(result)
} catch {
print("failed to convert \(error) ")
completion(nil)
}
}
task.resume()
}
}
}
You need to access the item of array to get the pageid or title like:
print(json.query.geosearch?[0].title ?? "")
Since the json returns an array
{
"batchcomplete": "",
"query": {
"geosearch": [
{
"pageid": 18618509,
"ns": 0,
"title": "Wikimedia Foundation",
"lat": 37.78916666666667,
"lon": -122.40333333333334,
"dist": 2.5,
"primary": ""
},
{
"pageid": 42936625,
"ns": 0,
"title": "Foxcroft Building",
"lat": 37.78916666666667,
"lon": -122.40333333333334,
"dist": 2.5,
"primary": ""
}
]
}
}
print(json.query.geosearch.self) //This will store the repsone in the let geosearch: [Geosearch]?
//You can check the individual result for the 1st and 2nd object
print(json.query.geosearch[0].pageid)//18618509
print(json.query.geosearch[0].title) //"Wikimedia Foundation"
print(json.query.geosearch[1].pageid)//42936625
print(json.query.geosearch[1].title) //"Foxcroft Building"

Looping through JSON array | Swift 5

I need to perform an action on each instance of trialid in the following JSON array - How can I loop through each instance of trialid using forEach? The goal is to pass each instance of trialid to another function that only excepts one value of trialid at a time.
The following is the structure of the JSON array:
[
{
"name": "mobile",
"orderid": 1,
"trialid": 27
},
{
"name": "mobile",
"orderid": 1,
"trialid": 33
}
]
The following is the what I am currently trying - how can foreach be performed here to loop through each object:
var structure = [testStructure]()
func fetch() {
guard let url = URL(string: "test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "id=1".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
let nav = try JSONDecoder().decode(structure, from: data)
structure.forEach ..
}
catch {
print(error)
}
}.resume()
}
struct testStructure: Decodable {
let name: String?
let orderid: Int?
let trialid: Int?
}
First of all please start names of classes, structures and other types with a capital letter.
If the API JSON response contains an array you need to decode it to the array type like this:
let nav = try JSONDecoder().decode([TestStructure].self, from: data)
then you can iterate through your decoded array of structures (full code):
import UIKit
struct TestStructure: Decodable {
let name: String?
let orderid: Int?
let trialid: Int?
}
func fetch() {
guard let url = URL(string: "test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "id=1".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
let nav = try JSONDecoder().decode([TestStructure].self, from: data)
nav.forEach { testStructure in
// do whatever you want here
if let trialid = testStructure.trialid {
processingTrialid(trialid)
}
}
}
catch {
print(error)
}
}.resume()
}
func processingTrialid(_ trialid: Int) {
print("trialid: ", trialid)
}

Always finding nil when checking Double in Swift

I've got the following code:
//create an NSURL
let url = NSURL(string: self.urlString)
//fetch the data from the url
URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in
//If the retrieved information is a JSON Object, and can be treated as an NSArray...
if let jsonObj = (try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSObject) {
let data = jsonObj.value(forKey: "pendingloads") as! NSArray
for item in data {
if let itemObject = item as? NSObject {
print("Tons value1: \(itemObject.value(forKey: "tons")!)")
let tons = itemObject.value(forKey: "tons") as? Double ?? nil
print("Tons value2: \(String(describing: tons))")
I'm doing this because I have it's possible to receive null from this data. My issue is that I always receive a double value (when the item returns one) in the first print, but nil in the second. Is it because the value of itemObject.value(forKey: "tons") is Optional?
I've attempted to force unwrap the value, but it then breaks when it is null. I need it to be nullable, but I've had trouble doing it in every documented way. I know there's a very simple answer to this, but I just haven't found it yet. Any help would be very appreciated.
The relevant JSON:
}
"pendingloads": [
{
"comment": "Test Comment",
"hauler": "Test Hauler",
"logs": [
{
"coords": "(25.123456, -120.123456)",
"type": "auth",
"timestamp": "2019-04-04 10:52:1554393131",
"device_id": "DEVICE-ID-DEVICE-ID-DEVICEID"
},
{
"coords": "(25.123456, -120.123456)",
"type": "update",
"timestamp": "2019-04-08 13:38:1554748736",
"device_id": "DEVICE-ID-DEVICE-ID-DEVICEID"
}
],
"tons": "12.800",
"load_id": 23,
"requires_correction": false,
"trailer_drop": true,
"gross": "25600.000",
"contract_id": 3679,
"scaleticket": "2134098",
"destination": "TEST DESTINATION",
"sale_id": 3961,
"tare": "0.000",
"net": "25600.000",
"cull": "157.000",
"product": "Test Product",
"operator_id": 2674,
"hauler_id": 617,
"timestamp": "2019-04-08 18:38:1554766680",
"driver": "Terry",
"ticket": 250,
"product_id": 3172,
"sale": "Test Sale",
"trailer": "013724589"
}
]
}
In the hopes of improvement, here is the updated code:
//create a URL
let url = URL(string: self.urlString)
//fetch the data from the url
URLSession.shared.dataTask(with: (url)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = (try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as AnyObject) {
let data = jsonObj.value(forKey: "pendingloads") as! Array<AnyObject>
for item in data {
let itemObject = item as AnyObject
You can try
struct Root: Codable {
let pendingloads: [Pendingload]
}
struct Pendingload: Codable {
let comment, hauler: String
let logs: [Log]
let tons: String
let loadId: Int
let requiresCorrection, trailerDrop: Bool
let gross: String
let contractId: Int
let scaleticket, destination: String
let saleId: Int
let tare, net, cull, product: String
let operatorId, haulerId: Int
let timestamp, driver: String
let ticket, productId: Int
let sale, trailer: String
}
struct Log: Codable {
let coords, type, timestamp, deviceId: String
}
let url = URL(string: self.urlString)!
URLSession.shared.dataTask(with:url) { (data, response, error) in
guard let data = data else { return }
do {
let res = JSONDecoder()
res.keyDecodingStrategy = .convertFromSnakeCase
let ss = try res.decode(Root.self, from:data)
print(ss)
}
catch {
print(error)
}
}.resume()

How to make the right API call?

I am trying to access fixer.io by making an API call. It is the first time than I am trying to do so, but I don't get the result wanted. I would like to get the "rate" and the "result" from this JSON file.
{
"success": true,
"query": {
"from": "GBP",
"to": "JPY",
"amount": 25
},
"info": {
"timestamp": 1519328414,
"rate": 148.972231
},
"historical": ""
"date": "2018-02-22"
"result": 3724.305775
}
The method that I have implemented is this one, but I can not figure out how to retrieve "rate" and "result" when making this API call.
extension APIsRuler {
func getExchangeRate(from: String, to: String, amount: String, callback: #escaping (Bool, ConversionResult?) -> Void) {
var request = URLRequest(url: APIsRuler.exchangeURL)
let body = "convert?access_key=\(APIsRuler.exchangeAPI)&from=\(from)&to=\(to)&amount=\(amount)"
request.httpMethod = "GET"
request.httpBody = body.data(using: .utf8)
let session = URLSession(configuration: .default)
task?.cancel()
task = session.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
guard let data = data, error == nil else {
return callback(false, nil)
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
return callback(false, nil)
}
guard let responseJSON = try? JSONDecoder().decode([String: Double].self, from: data),
let rate = responseJSON["rate"],
let result = responseJSON["result"] else {
return callback(false, nil)
}
let conversionResult = ConversionResult(exchangeRate: rate, exchangeResult: result)
callback(true, conversionResult)
}
}
task?.resume()
}
}
Use a real model object, like this:
struct Conversion: Codable {
let success: Bool
let query: Query
let info: Info
let historical, date: String
let result: Double
}
struct Info: Codable {
let timestamp: Int
let rate: Double
}
struct Query: Codable {
let from, to: String
let amount: Int
}
and parse your response into it using JSONDecoder:
do {
let conversion = try JSONDecoder().decode(Conversion.self, from: data)
let rate = conversion.info.rate
let result = conversion.result
} catch { print(error) }
You are mixing up two different APIs.
Either use JSONSerialization, the result is a dictionary and you get the values by key and index subscription. And you have to downcast every type and consider the nested rate value.
guard let responseJSON = try? JSONSerialization.jsonObject(with: data) as? [String:Any],
let info = responseJSON["info"] as? [String:Any],
let rate = info["rate"] as? Double,
let result = responseJSON["result"] as? Double else {
return callback(false, nil)
}
let conversionResult = ConversionResult(exchangeRate: rate, exchangeResult: result)
callback(true, conversionResult)
Or use JSONDecoder then you have to create structs, decoding to [String:Double] can work only if all values in the root object are Double which is clearly not the case.
struct Root: Decodable {
let info: Info
let result: Double
}
struct Info: Decodable {
let rate: Double
}
guard let responseJSON = try? JSONDecoder().decode(Root.self, from: data) else {
return callback(false, nil)
}
let conversionResult = ConversionResult(exchangeRate: responseJSON.info.rate, exchangeResult: responseJSON.result)
callback(true, conversionResult)
The code is only an example to keep your syntax. Practically you are strongly discouraged from using try? when decoding JSON. Always catch and handle errors
do {
let responseJSON = try JSONDecoder().decode(Root.self, from: data)
let conversionResult = ConversionResult(exchangeRate: responseJSON.info.rate, exchangeResult: responseJSON.result)
callback(true, conversionResult)
} catch {
print(error)
return callback(false, nil)
}