I have such array in from api:
[{
data = "";
datetime = "23.07.2020 12:09";
id = 340593;
status = "My current task is";
},...]
I have created such struct:
struct RemarksModel {
let id:Int
let status,datetime:String
let data:String?
}
And here I make request:
AF.request(URLRequest(url:Pathes.init(endpoint: "notepad/\(noteModel?.id ?? 0)/documentation").resourseUrl),
interceptor: CallInterceptor.init(method:HTTPMethod.get)).responseJSON(completionHandler: { (response) in
print(response.description)
switch response.result{
case .success(let array):
let remarksData = array as? [RemarksModel]
let json = response.data as? [RemarksModel]
print(json?.count)
// if remarksData?.count ?? 1 > 0{
// self.remarksArray += remarksData!
// self.tableView.reloadData()
// }
case .failure(let error):
print(error.localizedDescription)
}
})
the problem is that I can't convert this array to array of my model objects. when I try to add all received data to array my app crashes because my array is nil, despite of json in logs. Maybe I have to use another way of converting received json array to objects array?
You can use directly .responseDecodable function instead of .responseData or .responseJSON after confirming RemarksModel to Codable (or just Decodable) protocol
.responseDecodable(of: [RemarksModel].self, queue: .main, decoder: JSONDecoder()) { (response) in
switch response.result {
case let .success(data):
// success
print(data)
case let .failure(error):
// error
print(error.localizedDescription)
}
}
You can add Codable protocol to RemarksModel and use .responseData instead of .responseJSON
.responseData { response in
switch response.result {
case let .success(data):
do {
let result = try JSONDecoder().decode([RemarksModel].self, from: data)
// success
} catch {
print("decoding error:\n\(error)")
// error
}
case let .failure(error):
// error
}
}
Related
I am learning Swift and trying to get elevation data based on coordinates from the Open Elevation API.
I found a code to make the request and decode the data using structs.
My problem is that the API result includes the information in an array:
{"results": [{"latitude": 41.161758, "longitude": -8.583933, "elevation": 117}]}
What I have been able to program so far does save the data as an array in json.results, but only with one index including all of the data:
[API.MyResult(latitude: 41.16176, longitude: -8.583933, elevation: 117)]
("API" is the name of the file)
Here is my code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = "https://api.open-elevation.com/api/v1/lookup?locations=41.161758,-8.583933"
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("error")
return
}
var result: Response?
//print(result)
do{
result = try JSONDecoder().decode(Response.self, from: data)
}
catch{
print(error.localizedDescription)
}
guard let json = result else {
return
}
print(json.results)
//print(json.results.latitude)
//print(json.results.longitude)
//print(json.results.elevation)
})
task.resume()
}
}
struct Response: Codable {
let results: [MyResult]
}
struct MyResult: Codable {
let latitude: Float
let longitude: Float
let elevation: Int
}
Trying to print out json.results.latitude leads to the error
"Value of type '[MyResult]' has no member 'latitude'"
I assume at some point, a variable has to be defined as an array.
What needs to be changed here?
result is indeed a single object, but the property results is an array (multiple objects).
A slightly different naming avoids the confusion.
Notes:
Never print literal "error" or error.localizedDescription in a Decoding context, always print the error instance.
Proceed to parse the result in the do scope
private func getData(from url: String){
guard let url = URL(string: url) else { print("Bad URL", url); return }
let task = URLSession.shared.dataTask(with: url) {data, _, error in
if let error = error { print(error); return }
do {
let response = try JSONDecoder().decode(Response.self, from: data!)
for result in response.results {
print(result.latitude)
print(result.longitude)
print(result.elevation)
}
}
catch {
print(error)
}
}
task.resume()
}
I am trying to parse the JSON object received from Web service which gives the result as JSON object of status and data.data again is a array of objects, from this object I want to take one element on the basis of which I have to fill a tableview.
web Service results comes as
{"status":1,"data":[{"service_id":"1","service_name":"Painter"},{"service_id":"2","service_name":"Plumber"},{"service_id":"3","service_name":"Electrician"},{"service_id":"4","service_name":"Handyman"},{"service_id":"5","service_name":"Carpenter"},{"service_id":"6","service_name":"Mason"}]}
parsing in swift I did as:--
created one class
class ABC: NSObject {
var service_name:String?
var service_id : Int?
init(service_name:String,service_id:Int) {
self.service_name = service_name
self.service_id = service_id
}
let myUrl = URL(string: "services.php");
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json
{
let status=parseJSON["status"] as? Int
let newdata : NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
self.model=(newdata.value(forKey: "data") as? [ABC])!
My problem is I am getting an array of objects in self.model as service_name and service_id keys.Now I want to take out one array of strings that contains all the service_name object values.Its saying not able to convert NSArray to swift array.
As already mentioned by others use (De)codable. It's easy to use and very comfortable. No type cast and no literal keys.
Create two structs, declare the members as non-optional constants with camelCased names and omit the initializer.
struct Root : Decodable {
let status : Int
let data : [Service]
}
struct Service : Decodable {
let serviceName : String
let serviceId : String
}
Then decode the JSON in another class or struct
let myUrl = URL(string: "services.php")
var request = URLRequest(url: myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let json = try decoder.decode(Root.self, from: data!)
let status = json.status
let newdata = json.data
} catch { print(error))
}
task.resume()
I would recommend to drop JSONSerialization and use Codable protocol instead with CodingKeys.
Here is a simple example to see how it works.
struct Service : Codable {
let id : Int
let name : String
// keys
private enum CodingKeys: String, CodingKey {
case id = "service_id"
case name = "service_name"
}
}
...
// assuming our data comes from server side
let jsonString = "..."
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(Service.self, from: jsonData)
print("Getting service: \(service.id) \(service.name)")
} catch {
print("Unexpected error: \(error).")
}
More documentation here.
Use native Swift type Dictionary everywhere you use NSDictionary now
Then get certain value for key by specifing key in dictionary's subscript
if let model = newData["data"] as? [ABC] {
self.model = model
}
Anyway, I would suggest you to start using Codable instead of JSONSerialization
struct Response: Decodable {
let status: Int
let data: [ABC]
}
struct ABC: Decodable {
let serviceName: String
let serviceId: String // note that `serviceId` isn’t `Int` But `String`
}
and then decode your data using JSONDecoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(Response.self, from: data!)
self.model = response.data
} catch { print(error) }
I am a beginner in swift and I have come across a case where I need to run AlamoFire GET request in a loop. But As far as i know , the Alamofire Get request is an Asynchronous call and calling it in loop will create a number of threads.
The requirement is :
I have an array of URLs
The array has to be traversed in a loop
URL on each index has to be called through AlamoFire GET request
The received data through the request will be added to an array of
data
After the last data is saved in the array, a
CollectionView.reload call should be called
Pseudo code is as follows:
for bookmarkURL in bookmarks
Alamofire.request(.GET, bookmarkURL ).responseJSON
{ response in switch response.result {
case .Success(let JSON):
x[i] = JSON as! [String : AnyObject] // saving data
case .Failure(let error):
print("the error for \(self.bookmarkURL) is \(error) ")
}
i++
if i == bookmarks.count{
collectionView.reload()
break}
}
can anyone tell me how should I do it in sequence?
Make bookmarks array as class property.
Add a class property index to store index value (Int) while traversing the bookmarks array
Add a function to make API call and call it recursively.
Here is the code:
func getData() {
var x = [[String: AnyObject]]()
Alamofire.request(.GET, bookmarks[index]).responseJSON { response in
switch response.result {
case .Success(let JSON):
x[self.index] = JSON as! [String : AnyObject] // saving data
self.index = self.index + 1
if self.index < self.bookmarks.count {
self.getData()
}else {
self.collectionView.reloadData()
}
case .Failure(let error):
print("the error for \(self.bookmarks[self.index]) is \(error) ")
if self.index < self.bookmarks.count {
self.getData()
}else {
self.collectionView.reloadData()
}
}
}
}
In this way your request will be made only when previous is completed thus making it sequentially instead of parallel and you can also reload the collectionView only after the last request is completed.
If you want to reload your collection view only after receiving whole the data, you can use dispatch_group like,
let group = dispatch_group_create() // create a group.
dispatch_group_enter(group) // enter the group just before create the request
dispatch_group_leave(group)//leave the group on completion closure
dispatch_group_notify(group, group) {
//reload your collection view
}
Full code
for bookmarkURL in bookmarks {
dispatch_group_enter(group)
Alamofire.request(.GET, bookmarkURL ).responseJSON { response in
switch response.result {
case .Success(let JSON):
x[i] = JSON as! [String : AnyObject] // saving data
case .Failure(let error):
print("the error for \(self.bookmarkURL) is \(error) ")
}
dispatch_group_leave(group)
}
}
dispatch_group_notify(group, group) {
collectionView.reload()
}
Note: if your bookmarks array is too large, better not to do it in a loop.
For that you can use following code
func loadBookmarkAtIndex(index: Int) {
if index >= bookmarks.count {
collectionView.reload()
return
}
let bookmarkURL = bookmarks[index]
Alamofire.request(.GET, bookmarkURL ).responseJSON { response in
switch response.result {
case .Success(let JSON):
x[index] = JSON as! [String : AnyObject] // saving data
case .Failure(let error):
print("the error for \(bookmarkURL) is \(error) ")
}
self.loadBookmarkAtIndex(index+1)
}
}
And call self.loadBookmarkAtIndex(0) from where you are initiating the data retrieve.
I think you should reload item of collection view each time you get the response from the sever regarding to the response received.
Here is the solution:
let x : NSMutableArray = NSMutableArray.init(capacity: bookmarks.count)
for var i in 0 ..< x.count {
Alamofire.request(.GET, bookmarkURL ).responseJSON
{ response in switch response.result {
case .Success(let JSON):
x.insertObject(JSON as! [String : AnyObject], atIndex:i)
// x[i] = // saving data
// <reload relative item cell of collectionView>
case .Failure(let error):
print("the error for \(self.bookmarkURL) is \(error) ")
}
i+=1
}
Hope above code will help you.
I am trying to pass my JSON array to an array called array so that I can then query the array with submission_id with value 27 to obtain the safety_rating_id, schedule_job_id, score and submission_id from this JSON https://codeshare.io/UqJMV but I'm being thrown this error
Cannot convert value of type '[JSON]' to expected argument type 'JSON'
Code to pass JSON to array:
var array: [JSON] = []
func getTask(onCompletion: () -> (), onError: ((NSError) -> ())? = nil) {
guard let endPoint = Data.sharedInstance.weeklyEndpoint
else { print("Empty endpoint"); return }
Alamofire.request(.GET, endPoint, encoding: .JSON)
.validate()
.responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
for (_,subJson):(String, JSON) in json {
if let date = subJson["start_date"].string{
self.date = date
}
if let building = subJson["building_name"].string{
self.building = building
}
if let jobId = subJson["schedule_job_id"].int {
self.jobIdArray.append(jobId)
}
if let tasks = subJson["tasks"].array{
Tasks.sharedInstance.datas = tasks
for building in tasks {
if let ratings = building["safety_ratings"].array{
print(ratings)
self.array.append(ratings)
}
}
}
}
onCompletion()
}
case .Failure(let error):
print("Request failed with error: \(error)")
onError?(error)
}
}
}
append() expects a single element (JSON), but ratings is an array ([JSON]).
That's what the error message says.
To append an array use appendContentsOf:
self.array.appendContentsOf(ratings)
So I'm using this function to pass the JSON Value (Any Object) in other class but it's full of issues so I can't get it working
func getSWPeopleAPI() -> NSMutableArray {
var JSON2: NSMutableArray
Alamofire.request(.GET, "http://swapi.co/api/people/1").responseJSON { Response in
print(Response.request)
print(Response.response)
print(Response.data)
print(Response.result)
if let JSON = Response.result.value{
JSON2 = JSON as! NSMutableArray
}
else{
return
}
}
return JSON2
}
Do you have a suggestion on how can I achieve this to pass it to my ApiManagerClass and what data type is best use when dealing with JSON?
You are doing in a wrong way! JSON must have a type of NSDictionary.
func getSWPeopleAPI(strUrl: String, completionHandler: (NSDictionary?, NSError?) -> ()) -> (){
Alamofire.request(.GET, strUrl).responseJSON { response in
switch response.result {
case .Success(let data):
let json = data as? NSDictionary
completionHandler(json, nil)
case .Failure(let error):
completionHandler(nil, error)
}
}
Here is the code with completion handler and error as specified!
How to Use:
postWebserviceWithURL(Url!) { (responseData, error) -> () in
//Code
}