fetching Json swift 5 - json

im trying to fetch some data from my api in swift 5
I get this error
Member 'success' in 'Result<[Suggestion], SuggestionError>' produces result of type 'Result', but context expects 'Result<[Suggestion],
and this is the function that gives me problems. if I comment the line giving me trouples it prints canNotProcessData
func getSuggestions (completion: #escaping(Result<[Suggestion], SuggestionError>) -> Void){
let dataTask = URLSession.shared.dataTask(with: resourceURL) { data, response, error in
guard let jsonData = data else{
completion(.failure(.noDataAvailable))
return
}
print(response!)
do{
let decoder = JSONDecoder()
let suggestionReponse = try decoder.decode(Suggestion.self, from: jsonData)
let suggestions = suggestionReponse
completion(.success(Suggestion))
}catch{
completion(.failure(.canNotProcessData))
}
}
dataTask.resume()
}
and this is my Suggestion struct
struct Suggestion:Decodable{
var id: Int
var hometeam: String
var awayteam: String
var hometeamLogo: String
var awayteamLogo: String
var bet: String
var value: Double
var stake: Int
var bookieOdds: Double
}
and this is an example of what I receive from my api
[
{
"id": 132,
"hometeam": "Afjet Afyonspor",
"awayteam": "Sancaktepe Belediyespor",
"hometeamLogo": "https://media.api-football.com/teams/3565.png",
"awayteamLogo": "https://media.api-football.com/teams/3604.png",
"bet": "2",
"value": 1.19,
"stake": 1,
"bookieOdds": 2.55
},
{
"id": 152,
"hometeam": "BolĂ­var",
"awayteam": "Blooming",
"hometeamLogo": "https://media.api-football.com/teams/3702.png",
"awayteamLogo": "https://media.api-football.com/teams/3701.png",
"bet": "2",
"value": 3.18,
"stake": 1,
"bookieOdds": 11
}
]

You should better show the whole message precisely:
Member 'success' in 'Result<[Suggestion], SuggestionError>' produces
result of type 'Result<Success, Failure>', but context expects
'Result<[Suggestion], SuggestionError>'
And it is obviously wrong as you are passing a type name here:
completion(.success(Suggestion))
But before the line, you have one thing wrong:
let suggestionReponse = try decoder.decode(Suggestion.self, from: jsonData)
The outermost structure of your JSON response is an array ([ ... ]) and you need to decode it as an Array of Suggestions, not a Suggestion:
Please try something like this:
func getSuggestions(completion: #escaping(Result<[Suggestion], SuggestionError>) -> Void){
let dataTask = URLSession.shared.dataTask(with: resourceURL) { data, response, error in
guard let jsonData = data else{
completion(.failure(.noDataAvailable))
return
}
print(response!)
do{
let decoder = JSONDecoder()
//### Use `[Suggestion].self`, not `Suggestion.self`
let suggestions = try decoder.decode([Suggestion].self, from: jsonData)
completion(.success(suggestions)) //###Use the response, not the type name
} catch {
print(error) //### This gives you some hints on how to solve the error cases
completion(.failure(.canNotProcessData))
}
}
dataTask.resume()
}

Related

I have nested data in a JSON file and I am using a nested struct. How can I access the values that are nested within the first struct in swift

Here is my code. I am pulling JSON data from CalorieNinjas API:
struct Result: Codable {
var items: [FoodItem]?
}
struct FoodItem: Codable {
var name: String?
var calories: String?
}
public class API {
func apiRequest(search: String, completion: #escaping (Result) -> ()) {
//URL
var query = search.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let url = URL(string: "https://calorieninjas.p.rapidapi.com/v1/nutrition?query=" + query!)
//URL REQUEST
var request = URLRequest(url: url!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
//Specify header
let headers = [
"x-rapidapi-key": "3be44a36b7msh4d4738910c1ca4dp1c2825jsn96bcc44c2b19",
"x-rapidapi-host": "calorieninjas.p.rapidapi.com"
]
request.httpMethod="GET"
request.allHTTPHeaderFields = headers
//Get the URLSession
let session = URLSession.shared
//Create data task
let dataTask = session.dataTask(with: request) { (data, response, error) in
let result = try? JSONDecoder().decode(Result.self, from: data!)
print(result)
DispatchQueue.main.async {
completion(result!)
}
}
//Fire off data task
dataTask.resume()
}
}
this is what my view looks like:
struct ContentView: View {
#State var result = Result()
#State private var searchItem: String = ""
var body: some View {
ZStack(alignment: .top) {
Rectangle()
.fill(Color.myPurple)
.ignoresSafeArea(.all)
VStack {
TextField("Enter food", text: $searchItem)
.background(Color.white)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
SearchButton()
.padding(.top)
.onTapGesture {
API().apiRequest(search: searchItem, completion: { (result) in
self.result = result
})
}
}
}
}
}
This is the output to the terminal as a result of my print statement so I know my data is being fetched and stored:
Optional(CalorieCountApp.Result(items: Optional([CalorieCountApp.FoodItem(name: Optional("pizza"), calories: Optional(262.9))])))
what I was trying to do was something like Text(result.items.name/calories) but I am not able to access the variables like that. I am new to swift and making apps as a whole any help is much appreciated
Looks like you have a few Optionals in there, which means you'll probably be using the ? operator to unwrap them.
Given your type, this should work:
let index = 0
let name = result?.items?[index].name // will be `String?`
let calories = result?.items?[index].calories // according to your code you provided, this says `String?` but in your console output it looks like `Double?`
or in your example:
Text(result?.items?[index].name ?? "unknown")
You might want to do some more reading about unwrapping Optionals or dealing with nil in Swift -- there are a few different strategies. For example, you can see I used ?? in the last example there.
Here's a helpful link: https://www.hackingwithswift.com/sixty/10/2/unwrapping-optionals

Accessing JSON Array from API response in Swift

I am brand new to writing swift so any tips on improvement/best practices are welcome but my main issue is that I am having trouble accessing a nested JSON array list. I am using this free API and trying to show a list of characters https://swapi.dev/api/people/
Please see the code snippet below.
When I print the type its : Optional<Any> and when i print json["results"] it prints the array like:
Optional(<__NSArrayI 0x600000fe31e0>(
{
"birth_year" = 19BBY;
created = "2014-12-09T13:50:51.644000Z";
....
I have tried several different things but have been unsuccessful. Could someone please give some advice on how I might iterate the list under json["results"?
func onLoad() -> Void {
let url = URL(string: "https://swapi.dev/api/people")
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Convert HTTP Response Data to a simple String
if let data = data {
// let json = try? JSONSerialization.jsonObject(with: data, options: [])
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
// try to read out a string array
print(type(of: json["results"]))
print(json["results"])
}
} catch let error as Error {
print("Failed to load: \(error.localizedDescription)")
}
}
}
task.resume()
}
Thanks for any help!
You should really be using Decodable rather than trying to parse it with JSON as that can easily lead to errors as you are accessing values by strings and it doesn't allow the IDE to help you.
You need to create some objects that describe what you are getting in your response.
Your main json response is made up of the following
{
"count": 82,
"next": "http://swapi.dev/api/people/?page=2",
"previous": null,
"results": [...]
}
This allows you to create a People struct that conforms to Decodable.
struct People: Decodable {
let count: Int
let next: URL?
let previous: URL?
let results: [Person]
}
The results array is really what you are after as that contains all the information about a person.
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "http://swapi.dev/api/planets/1/",
"films": [
"http://swapi.dev/api/films/1/",
"http://swapi.dev/api/films/2/",
"http://swapi.dev/api/films/3/",
"http://swapi.dev/api/films/6/"
],
"species": [],
"vehicles": [
"http://swapi.dev/api/vehicles/14/",
"http://swapi.dev/api/vehicles/30/"
],
"starships": [
"http://swapi.dev/api/starships/12/",
"http://swapi.dev/api/starships/22/"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-20T21:17:56.891000Z",
"url": "http://swapi.dev/api/people/1/"
}
We can represent this with the following struct called Person that also conforms to Decodable
struct Person: Decodable {
let name: String
let height: String
let mass: String
let hairColor: String
let skinColor: String
let birthYear: String
let gender: Gender
let homeworld: String
let films: [URL]
let species: [URL]
let vehicles: [URL]
let starships: [URL]
let created: Date
let edited: Date
let url: URL
}
enum Gender: String, Decodable {
case male
case female
case unknown = "n/a"
}
Note a couple of differences between the names in the struct and the names in the object that you are getting back. eg hair_color (snakecase) and hairColor (camelCase) In Swift it is common to write it the latter way and when we use decodable we can tell our decoder to use a custom key decoding strategy. Also note that I have used an enum for Gender. This isn't required and we could have just used a String. Also note that created and edited are Dates, however they are not iso8601 compliant but we can also specify a custom date decoding strategy.
Here is how we can decode the data that you have received.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let people = try decoder.decode(People.self, from: data)
Now we can put this all together in your network request to get the following:
func onLoad() {
let url = URL(string: "https://swapi.dev/api/people")
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Convert HTTP Response Data to a simple String
if let data = data {
do {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let people = try decoder.decode(People.self, from: data)
people.results.forEach { person in print(person) }
} catch {
print("Failed to load: \(error)")
}
}
}
task.resume()
}
Cast results as an Array of Dictionary. Here's how
if let data = data {
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let results = json["results"] as? [[String: Any]] {
for result in results {
print(result)
}
}
} catch {
print("Failed to load: \(error.localizedDescription)")
}
}
Better Approach: Use Codable, JSONSerialization feels bit outdated.
Related Links:
https://developer.apple.com/documentation/swift/codable
https://www.swiftbysundell.com/basics/codable/

decoding a Codable in Swift

Having troubles getting this to work: I am trying to abstract the JSON-decoding into a function, taking as arguments a Codable plus some Data.
Therefore, I need to have the following function-signature if possible for this:
func doTheJSONDecoding(cdbl: Codable, data: Data) {...}
Here is my code, starting with the data-model. There are two examples below....
import UIKit
import Foundation
struct MyStructCodable : Codable {
let items : [MyValue]?
}
struct MyValue : Codable {
let value : String?
}
let dta: Data = """
{
"items": [
{
"value": "Hello1"
}
]
}
""".data(using: .utf8)!
Then the two examples:
// Example 1: this code works fine !!!!!!!!!!!!!!!!!!!!!!!!
let decoder = JSONDecoder()
do {
let result = try decoder.decode(MyStructCodable.self, from: dta)
print(result.items?[0].value ?? "")
} catch {
print(error)
}
// above code prints: Hello1
// Example 2: this code does not work - WHY ???????????????
func doTheJSONDecoding(cdbl: Codable, data: Data) {
let decoder = JSONDecoder()
do {
let result = try decoder.decode(cdbl, from: data)
print(result.items?[0].value ?? "")
} catch {
print(error)
}
}
let myValue = MyValue(value: "Hello2")
let myStructyCodable = MyStructCodable(items: [myValue])
doTheJSONEncoding(cdbl: myStructyCodable, data: dta)
The error thrown is inside the function, it says:
Is there any way so that I can keep the function signature (i.e. func doTheJSONDecoding(cdbl: Codable, data: Data) and still getting this to work ?? Any help appreciated.
Here is my attempt to get your func to work, it can probably be improve but it does return a properly decoded object. Note that it takes the type of object rather than an object and it is that type T that implement Decodable.
func doTheJSONEncoding<T: Decodable>(cdbl: T.Type, data: Data) -> T? {
let decoder = JSONDecoder()
do {
let result = try decoder.decode(cdbl.self, from: data)
return result
} catch {
print(error)
}
return nil
}
//testing it
let myValue = MyValue(value: "Hello2")
let myStructyCodable = MyStructCodable(items: [myValue])
let decoded = doTheJSONEncoding(cdbl: MyStructCodable.self, data: dta)
print(decoded?.items?[0].value ?? "")

Read from API JSON using Alamofire Swift 4

i have hard time to read JSON using Alamofire. below is the structure of the API response
{
"data": [
{
"order": {
"id": 258,
"created_at": "2018-07-01T14:51:05+08:00",
"user_id": "1234"
},
"transactions": [
{
"transaction_type": "rent",
"cabinet_id": "02110A0000C6",
"jack_id": 1
}
]
}
]
}
Basically, i need to print out only the array of transactions and also print one by one the transaction_type, cabinet_id, and jack_id
The previous one i just manage to print the api response using code below
Alamofire.request(WalletRouter.urlUserActiveOrder(id: userId)).responseJSON { response in
if let value = response.result.value {
let dict = value as? Dictionary<String, AnyObject>
//print (value)
if let innerDict = dict!["data"] {
print (innerDict) //manage to return the value
let data = innerDict["transactions"] as! NSArray //get error here
print (data)
}
}
//do your json stuff
else if (response.result.isFailure) {
//Manager your error
switch (response.error!._code){
case NSURLErrorTimedOut:
//Manager your time out error
break
case NSURLErrorNotConnectedToInternet:
//Manager your not connected to internet error
break
default:
print ("error")
}
}
}
i have already spent 6 hour to solve this, but still failed to get the value.
Create a struct class like below
struct Result: Decodable {
struct data12: Decodable {
struct transaction: Decodable {
let transactionType:String
let cabinetId:String
let jackId:Int
}
let transactions:[transaction]
}
let data:[data12]
}
Data fetching like this
do {
let data1 = try Data(contentsOf: URL(fileURLWithPath: path), options: [])
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let courses = try decoder.decode(Result.self, from: data1)
print(courses)
} catch {
print(error)
}
if anyOne think my approach is wrong please update my answer TIA

Swift JSON parsing

I am working on an iOS weather app and need some help getting some values with JSON. I am trying to pull the value of id in the weather object. I am getting a value of nil when I debug. Could someone please help me with the logic? Here is the JSON request and Swift code.
{
"coord":{
"lon":138.93,
"lat":34.97
},
"weather":[
{
"id":800,
"main":"Clear",
"description":"clear sky",
"icon":"01n"
}
],
"base":"cmc stations",
"main":{
"temp":292.181,
"pressure":1005.21,
"humidity":100,
"temp_min":292.181,
"temp_max":292.181,
"sea_level":1014.59,
"grnd_level":1005.21
},
"wind":{
"speed":3.41,
"deg":78.0005
},
"clouds":{
"all":0
},
"dt":1464801034,
"sys":{
"message":0.003,
"country":"JP",
"sunrise":1464723086,
"sunset":1464774799
},
"id":1851632,
"name":"Shuzenji",
"cod":200
}
Here is my Swift snippet:
let requestURL: NSURL = NSURL(string: "http://api.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=6361e893fa064b1bfeaca686cd0929cc")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("JSON Downloaded Sucessfully.")
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let today = json["weather"] as? [[String: AnyObject]] {
//this is pulling 4 key value pairs
for weather in today {
let id = weather["id"] as? String
self.trumpDescription.text=id;
print(id)
}
}
}
catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
}
The problem lies in your cast to a string. If you do not cast the id as a String, it will print "800" as an Int.
When you are grabbing the id from the json and converting it to a string, it reads the string and includes the new line character.
I played around in the playground
So you are correctly getting to the spot in the json, but you need to unwrap your optional.
You can also use the nil coalescing operator to unwrap optionals so you don't have to have a bunch of if lets:
let id = weather["id"] as? String ?? ""
Which will either set id to the value or to "" if it doesn't exist.
try this in your code:
let id = weather["id"]?.stringValue
instead of this:
let id = weather["id"] as? String
And see the magic!
Edit for explanation:
As this answer worked for you, let me tell you why it did.
The id is being sent as integer from the server side. When you do weather["id"] it returns object of type AnyObject?. When you do weather["id"] as? String the casting fails thus you were getting nil.