Decode simple API array Swift - 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

Related

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

Array vs Dictionary response structures with JSONDecoder

Got the following data model:
class ResponseMultipleElements<Element: Decodable>: Decodable {
let statuscode: Int
let response_type: Int
let errormessage: String?
let detailresponse: Element?
}
class Element<T: Decodable>: Decodable {
let count: String;
let element: T?
}
For the following API response structure:
{
"statuscode": 200,
"response_type": 3,
"errormessage": null,
"detailresponse": {
"count": "1",
"campaigns": [
{
"id": 1,
"name": "Foo",
"targetagegroup": null,
"creator":...
...
}
}
}
I'm triggering JSONDecoder like this:
class APIService: NSObject {
func getCampaignList(completion: #escaping(Result<[Campaign], APIError>) -> Void) {
guard let endpoint = URL(string: apiBaseUrlSecure + "/campaignlist") else {fatalError()}
var request = URLRequest(url: endpoint)
request.addValue("Bearer " + UserDefaults.standard.string(forKey: "authtoken")!, forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let jsonData = data
else { print("ERROR: ", error ?? "unknown error"); completion(.failure(.responseError)); return }
do {
let response = try JSONDecoder().decode(ResponseMultipleElements<[Campaign]>.self, from: jsonData)
completion(.success(response.detailresponse!))
} catch {
print("Error is: ", error)
completion(.failure(.decodingError))
}
}
dataTask.resume()
}
...
}
And I'm finally trying to make use of the decoded campaign object like this
class CoopOverviewViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
//do stuff
// load Campaigns
self.apiService.getCampaignList(completion: {result in
switch result {
case .success(let campaigns):
DispatchQueue.main.async {
print("CAMPAIGN DATA: ", campaigns[0].name)
}
case .failure(let error):
print("An error occured \(error.localizedDescription)")
}
})
...
}
Now I've got 2 questions:
1)
let element: T?
is actually called "campaigns" in the api response for this call. However, it could be cooperations, payments, etc. in other api responses with that same ResponseMultipleElements surrounding structure. Is there a way to make the key swappable here, like I've done with the value with the use of generics? If not, how else would I solve that problem?
2) I'm getting this error:
typeMismatch(Swift.Array<Any>,
Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "detailresponse", intValue: nil)],
debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
I've told Swift that the "campaigns" part of the detailresponse is an Array of campaign objects - at least that's my understanding when looking at the api response. However, the error seems to say it's a dictionary. First, I don't get why that is and would really like to understand it. Second, I don't know how to tell it that it should expect a dictionary instead of an array then - getting confused with generics here a bit.
Thank you so much for your help in advance!
This is an approach to add a custom key decoding strategy to map any CodingKey but count in detailresponse to fixed value element.
First of all create a custom CodingKey
struct AnyCodingKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) {
return nil
}
}
Then create the structs similar to Sh_Khan's answer, in most cases classes are not needed
struct ResponseMultipleElements<T: Decodable>: Decodable {
let statuscode : Int
let response_type : Int
let errormessage : String?
let detailresponse : Element<T>
}
struct Element<U: Decodable>: Decodable {
let count : String
let element : U
}
struct Campaign : Decodable {
let id : Int
let name : String
let targetagegroup : String?
}
Now comes the funny part. Create a custom key decoding strategy which returns always element for the CodingKey in detailresponse which is not count
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom { codingKeys in
let lastKey = codingKeys.last!
if lastKey.intValue != nil || codingKeys.count != 2 { return lastKey }
if lastKey.stringValue == "count" { return lastKey }
return AnyCodingKey(stringValue: "element")!
}
let result = try decoder.decode(ResponseMultipleElements<[Campaign]>.self, from: data)
completion(.success(result.detailresponse.element))
} catch {
print("Error is: ", error)
completion(.failure(error))
}

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