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