Read from API JSON using Alamofire Swift 4 - json

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

Related

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/

fetching Json swift 5

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

reduce function is printing an empty dictionary [:]

I have reduced my dictionary keys successfully in this question as pseudo-code without a real json model. The goal which I accomplished in the previous question is to return only the keys that have matching values. So the output is a dictionary that looks something like this ["WoW": ["#jade", "#kalel"]. Exactly what I needed. Of course there could be other matches and I'd like to return those as well.
Now that I have a proper json model, the reduce function is printing out an empty dictionary [:]. Is it the type in .reduce(into: [String:[String]]() that is causing the issue?
All the data is printing so the json and model structure must be correct.
json
[
{
"id": "tokenID-tqkif48",
"name": "#jade",
"game": "WoW",
"age": "18"
},
{
"id": "tokenID-fvkif21",
"name": "#kalel",
"game": "WoW",
"age": "20"
}
]
UserModel
public typealias Users = [UserModel]
public struct UserModel: Codable {
public let name: String
public let game: String
// etc...
enum CodingKeys: String, CodingKey {
case name
case game
// etc...
Playground
guard let url = Bundle.main.url(forResource: "Users", withExtension: "json") else {
fatalError()
}
guard let data = try? Data(contentsOf: url) else {
fatalError()
}
let decoder = JSONDecoder()
do {
let response = try decoder.decode([UserModel].self, from: data)
for userModel in response {
let userDict: [String:String] = [ userModel.name:userModel.game ]
let reduction = Dictionary(grouping: userDict.keys) { userDict[$0] ?? "" }.reduce(into: [String:[String]](), { (result, element) in
if element.value.count > 1 {
result[element.key] = element.value
}
})
// error catch etc
}
Your code is too complicated. You can group the array by game simply with
let response = try decoder.decode([UserModel].self, from: data)
let reduction = Dictionary(grouping: response, by: {$0.game}).mapValues{ usermodel in usermodel.map{ $0.name}}
UPDATE I may be mistaking what you want to get. There's another code below and please check the results and choose one you want.
If you want to use reduce(into:updateAccumulatingResult:), you can write something like this.
do {
let response = try decoder.decode([UserModel].self, from: data)
let userArray: [(name: String, game: String)] = response.map {($0.name, $0.game)}
let reduction = userArray.reduce(into: [String:[String]]()) {result, element in
if !element.game.isEmpty {
result[element.name, default: []].append(element.game)
}
}
print(reduction)
} catch {
print(error)
}
If you prefer an initializer of Dictionary, this may work:
do {
let response = try decoder.decode([UserModel].self, from: data)
let userArray: [(name: String, games: [String])] = response.map {
($0.name, $0.game.isEmpty ? [] : [$0.game])
}
let reduction = Dictionary(userArray) {old, new in old + new}
print(reduction)
} catch {
print(error)
}
Both output:
["#jade": ["WoW"], "#kalel": ["WoW"]]
Anyway, your way of combining loop, Dictionary(grouping:) and reduce(into:) in addition of userDict.keys is making things too complex than they should be.
ADDITION When you want to get a Dictionary with keys as games:
do {
let response = try decoder.decode([UserModel].self, from: data)
let userArray: [(game: String, name: String)] = response.compactMap {
$0.game.isEmpty ? nil : ($0.game, $0.name)
}
let reduction = userArray.reduce(into: [String:[String]]()) {result, element in
result[element.game, default: []].append(element.name)
}
print(reduction)
} catch {
print(error)
}
Or:
do {
let response = try decoder.decode([UserModel].self, from: data)
let userArray: [(game: String, names: [String])] = response.compactMap {
$0.game.isEmpty ? nil : ($0.game, [$0.name])
}
let reduction = Dictionary(userArray) {old, new in old + new}
print(reduction)
} catch {
print(error)
}
Output:
["WoW": ["#jade", "#kalel"]]

Can Swift 4's JSONDecoder be used with Firebase Realtime Database?

I am trying to decode data from a Firebase DataSnapshot so that it can be decoded using JSONDecoder.
I can decode this data fine when I use a URL to access it with a network request (obtaining a Data object).
However, I want to use the Firebase API to directly obtain the data, using observeSingleEvent as described on this page.
But, when I do this, I cannot seem to convert the result into a Data object, which I need to use JSONDecoder.
Is it possible to do the new style of JSON decoding with a DataSnapshot? How is it possible? I can't seem to figure it out.
I have created a library called CodableFirebase that provides Encoders and Decoders that are designed specifically for Firebase.
So for the example above:
import Firebase
import CodableFirebase
let item: GroceryItem = // here you will create an instance of GroceryItem
let data = try! FirebaseEncoder().encode(item)
Database.database().reference().child("pathToGraceryItem").setValue(data)
And here's how you will read the same data:
Database.database().reference().child("pathToGraceryItem").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else { return }
do {
let item = try FirebaseDecoder().decode(GroceryItem.self, from: value)
print(item)
} catch let error {
print(error)
}
})
I've converted Firebase Snapshots using JSONDecoder by converting snapshots back to JSON in Data format. Your struct needs to conform to Decodable or Codable. I've done this with SwiftyJSON but this example is using JSONSerialization and it still works.
JSONSnapshotPotatoes {
"name": "Potatoes",
"price": 5,
}
JSONSnapshotChicken {
"name": "Chicken",
"price": 10,
"onSale": true
}
struct GroceryItem: Decodable {
var name: String
var price: Double
var onSale: Bool? //Use optionals for keys that may or may not exist
}
Database.database().reference().child("grocery_item").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }
do {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
let groceryItem = try JSONDecoder().decode(GroceryItem.self, from: jsonData)
print(groceryItem)
} catch let error {
print(error)
}
})
Please note that if your JSON keys are not the same as your Decodable struct. You'll need to use CodingKeys. Example:
JSONSnapshotSpinach {
"title": "Spinach",
"price": 10,
"onSale": true
}
struct GroceryItem: Decodable {
var name: String
var price: Double
var onSale: Bool?
enum CodingKeys: String, CodingKey {
case name = "title"
case price
case onSale
}
}
You can find more information on this using Apple Docs here.
No. Firebase returns a FIRDataSnapshot that can't be decodable. You can use this structure however, which is pretty simple and easy to understand:
struct GroceryItem {
let key: String
let name: String
let addedByUser: String
let ref: FIRDatabaseReference?
var completed: Bool
init(name: String, addedByUser: String, completed: Bool, key: String = "") {
self.key = key
self.name = name
self.addedByUser = addedByUser
self.completed = completed
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
name = snapshotValue["name"] as! String
addedByUser = snapshotValue["addedByUser"] as! String
completed = snapshotValue["completed"] as! Bool
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"name": name,
"addedByUser": addedByUser,
"completed": completed
]
}
}
And use toAnyObject() to save your item:
let groceryItemRef = ref.child("items")
groceryItemRef.setValue(groceryItem.toAnyObject())
Source: https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
Or you can use this solution for children
extension DatabaseReference {
func makeSimpleRequest<U: Decodable>(completion: #escaping (U) -> Void) {
self.observeSingleEvent(of: .value, with: { snapshot in
guard let object = snapshot.children.allObjects as? [DataSnapshot] else { return }
let dict = object.compactMap { $0.value as? [String: Any] }
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
let parsedObjects = try JSONDecoder().decode(U.self, from: jsonData)
completion(parsedObjects)
} catch let error {
print(error)
}
})
}
}
and use
self.refPriceStatistics.child(productId).makeSimpleRequest { (parsedArray: [YourArray]) in
callback(parsedArray)
}
If your data type is Codable you can use the following solution to decode directly. You do not need any plugin. I used the solution for Cloud Firestore.
import Firebase
import FirebaseFirestoreSwift
let db = Firestore.firestore()
let query = db.collection("CollectionName")
.whereField("id", isEqualTo: "123")
guard let documents = snapshot?.documents, error == nil else {
return
}
if let document = documents.first {
do {
let decodedData = try document.data(as: ModelClass.self)
// ModelClass a Codable Class
}
catch let error {
//
}
}
You can convert the value returned by Firebase to Data, and then decode that.
Add this extension to your project:
extension Collection {
//Designed for use with Dictionary and Array types
var jsonData: Data? {
return try? JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
}
}
Then use it to convert the value of the observed snapshot into data, which can then be decoded:
yourRef.observe(.value) { (snapshot) in
guard snapshot.exists(),
let value = snapshot.value as? [String],
let data = value.jsonData else {
return
}
//cast to expected type
do {
let yourNewObject = try JSONDecoder().decode([YourClass].self, from: data)
} catch let decodeError {
print("decodable error")
}
}
You can use this library CodableFirebase or the following extension can be helpful.
extension JSONDecoder {
func decode<T>(_ type: T.Type, from value: Any) throws -> T where T : Decodable {
do {
let data = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
let decoded = try decode(type, from: data)
return decoded
} catch {
throw error
}
}

Looping through JSON object in Swift

I got this JSON object which I sent from my server to my Swift application.
{
"625289": {
"id": 1,
"subject": "Hello World"
},
"625277": {
"id": 2,
"subject":"Bye World!"
}
}
So i tried to get the subject for each result ("625289" and "625277") by doing as below in my Swift class:
struct Resultat : Decodable {
let subject: String
}
var result = [Resultat]()
let urlll = URL(string:"http://localhost:8888/api/pouet.php")
URLSession.shared.dataTask(with: urlll!) { (data, response, error) in
do {
print("coucoulol")
//print(response)
self.result = try JSONDecoder().decode([Resultat].self, from: data!)
print(self.result)
for eachTicket in self.result {
print(eachTicket.subject)
}
} catch {
print("error"+error.localizedDescription)
}
}.resume()
However, when I tried to execute the code, it says "The data couldn’t be read because it isn’t in the correct format." From what I understand, the loop for in the code is suffice to get the values in the arrays or maybe I'm wrong. Any help is appreciated, thanks.
The root object is a dictionary. You can decode the object to [String:Resultat]. The dictionary contains also dictionaries. An array is not involved.
struct Resultat : Decodable {
let subject: String
let id : Int
}
...
let result = try JSONDecoder().decode([String:Resultat].self, from: data!)
for (key, value) in result {
print(key, value.subject)
}
You can try using SwiftyJSON below
$0.0 = Key
$0.1 = value
let data = JSON(result)
data.dictionaryValue.forEach({
print($0.1)
})