Always finding nil when checking Double in Swift - 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()

Related

JSONDecoder does not work for Swift code (SWIFT UI)

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")
}

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)
}

Decode simple API array Swift

I am struggling to decode a specific object returned by an API, I can't figure out the correct struct.
What's wrong?
Here is the returned API JSON:
{
"votes": [
{
"votesId": "1",
"vote_nb": "6",
"current_time": "0",
"trend_up": "0",
"trend_down": "0",
"position_hold": "0",
"position_buy": "0",
"position_sell": "0"
}]
}
And my code to decode and fetch:
struct VoteData : Codable {
let votes : [Votes]
}
struct Votes : Codable {
let vote_nb : String
}
func fetchData(completion: #escaping (VoteData?, Error?) -> Void) {
let url = URL(string: "...")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil, error ?? APIError.unknownNetworkError)
return
}
do {
let result = try JSONDecoder().decode(VoteData.self, from: data); completion(result, nil)
let vote_nb = result.votes.vote_nb // Value of type '[Votes]' has no member 'vote_nb'
print("VOTE NUMBER: ", vote_nb)
} catch let parseError {
completion(nil, parseError)
}
}
task.resume()
}
First select a particular member of the array, then check that property of THE member.
Change this line:
let vote_nb = result.votes.vote_nb
For this:
let vote_nb = result.votes[0].vote_nb

Json Object mapping Swift

I would like to do a Post request to a url however i just learnt of new concept in Swift called Object mapping. All the tutorial i have learnt how to map the Json object to swift structs or classes but non shows me how to use these objects once they are mapped.
How do i access these objects such that i use them when doing a post request.
Here is example Json:
{
"country": "string",
"dateOfBirth": "string",
"email": "string",
"gender": "string",
"id": "string",
"interaction": {
"deviceOS": "string",
"deviceType": "string",
"interactionLocation": "string",
"interactionType": "string",
"timeStamp": "string"
},
"name": "string",
"occupation": "string",
"passportOrIDimage": "string",
"phoneNumber": "string",
"physicalAddress": "string",
"salutation": "string",
"surname": "string",
"userlogin": {
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true,
"password": "string",
"roles": [
{
"roleName": "string"
}
],
"username": "string"
}
}
Example Object mapping in swift 4 :
struct Register: Codable {
let country: String?
let dateOfBirth: String?
let email: String?
let gender: String?
let id: String?
let interaction: Interaction?
let name: String?
let occupation: String?
let passportOrIDimage: String?
let phoneNumber: String?
let physicalAddress: String?
let salutation: String?
let surname: String?
let userlogin: Userlogin?
enum CodingKeys: String, CodingKey {
case country = "country"
case dateOfBirth = "dateOfBirth"
case email = "email"
case gender = "gender"
case id = "id"
case interaction = "interaction"
case name = "name"
case occupation = "occupation"
case passportOrIDimage = "passportOrIDimage"
case phoneNumber = "phoneNumber"
case physicalAddress = "physicalAddress"
case salutation = "salutation"
case surname = "surname"
case userlogin = "userlogin"
}
}
struct Interaction: Codable {
let deviceOS: String?
let deviceType: String?
let interactionLocation: String?
let interactionType: String?
let timeStamp: String?
enum CodingKeys: String, CodingKey {
case deviceOS = "deviceOS"
case deviceType = "deviceType"
case interactionLocation = "interactionLocation"
case interactionType = "interactionType"
case timeStamp = "timeStamp"
}
}
struct Userlogin: Codable {
let accountNonExpired: Bool?
let accountNonLocked: Bool?
let credentialsNonExpired: Bool?
let enabled: Bool?
let password: String?
let roles: [Role]?
let username: String?
enum CodingKeys: String, CodingKey {
case accountNonExpired = "accountNonExpired"
case accountNonLocked = "accountNonLocked"
case credentialsNonExpired = "credentialsNonExpired"
case enabled = "enabled"
case password = "password"
case roles = "roles"
case username = "username"
}
}
struct Role: Codable {
let roleName: String?
enum CodingKeys: String, CodingKey {
case roleName = "roleName"
}
}
// MARK: Convenience initializers
extension Register {
init(data: Data) throws {
self = try JSONDecoder().decode(Register.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
extension Interaction {
init(data: Data) throws {
self = try JSONDecoder().decode(Interaction.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
extension Userlogin {
init(data: Data) throws {
self = try JSONDecoder().decode(Userlogin.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
extension Role {
init(data: Data) throws {
self = try JSONDecoder().decode(Role.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
I would to put the data i want to post in the params dictionary this where i am not sure how to do so using object mapping.
My post request :
var request = URLRequest(url: URL(string: "http://testURL")!)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject: params as Any, options: [])
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
print(response!)
do {
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, AnyObject>
let alert = UIAlertController(title: "Response", message: "message", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
print(json)
} catch {
print("error")
}
})
task.resume()
}
if you observe in your struct
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
In your post request you can use like this
request.httpBody = try? objectOfCodableStruct.jsonData()
Hope it is helpful

Issues parsing JSON data with Swift from API

I am attempting to get data from an API and bring it into swift. I have managed to connect to th API and get the data come into the console however when I attempt to parse the data I have an issue - below is the function that does the connection and should parse the data.
I am having an issue with the line:
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSArray
It is producing the fatal error SIGABRT when running the app (the function parseData() is called within viewDidLoad()) and the console produces the following - Could not cast value of type '__NSDictionaryI' (0x103b7f288) to 'NSArray' (0x103b7ee28).
func parseData() {
let url = "https://api.***"
var request = URLRequest(url: URL(string: url)!)
let headers = [
"user": "***",
"auth": "***"
]
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request) { (data, response, error) in
if (error != nil) {
print("Error")
} else {
do {
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSArray
for eachFetchedOpportunity in fetchedData {
let eachOpportunity = eachFetchedOpportunity as! [String : Any]
let opportunity = eachOpportunity["subject"] as! String
let startDate = eachOpportunity["starts_at"] as! String
self.fetchedOpportunitys.append(Opportunity(opportunityName: opportunity, startDate: startDate))
}
print(self.fetchedOpportunitys)
} catch {
print("Error")
}
}
}
task.resume()
}
Here is the start of the returned data (I haven't included all of the data as some of it is private and unnecessary) from the server that I have accessed through postman - I believe my problem may reside in the fact that there is a mix of Dictionaries and Arrays here and I am unaware on how to correctly parse the data if this is the case.
{
"opportunities": [
{
"id": 3,
"store_id": 1,
"project_id": null,
"member_id": 5,
"billing_address_id": 5,
"venue_id": null,
"tax_class_id": 1,
"subject": "***",
"description": "",
"number": "00000000**",
"starts_at": "2017-05-27T08:00:00.000Z",
"ends_at": "2017-05-27T16:00:00.000Z",
"charge_starts_at": "2017-05-27T08:00:00.000Z",
"charge_ends_at": "2017-05-27T16:00:00.000Z",
"ordered_at": "2017-05-25T21:10:00.000Z",
"quote_invalid_at": null,
"state": 3,
...
...
}
],
"meta": {
"total_row_count": 2,
"row_count": 2,
"page": 1,
"per_page": 12
}
}
Your JSON response is Dictionary not Array and the array you are looking for is opportunities that you need to get from the dictionary.
do {
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any]
let opportunities = fetchedData["opportunities"] as? [[String:Any]] ?? []
for opportunity in opportunities {
let subject = opportunity["subject"] as? String ?? "Default Subject"
let startDate = opportunity["starts_at"] as? String ?? "Default Date"
self.fetchedOpportunitys.append(Opportunity(opportunityName: subject, startDate: startDate))
}
}
catch {
print("Error")
}