this is a pretty basic question of the syntax for deserializing responsejson collection in alamofire and swift
I have a GET service that returns an array of user objects e.g.
[
{"uname": "bob"},
{"uname": "jane"}
]
and here is my request
Alamofire.request(.GET, url , encoding:.JSON)
.responseJSON(options: .MutableContainers, completionHandler:{ (request, response, JSON, error) -> Void in
let result = JSON as? [Dictionary<String,String>]
if (response!.statusCode == 200 && error == nil){
//how should I deserialize the result here to return [String]
callback(success: true, errorMsg: nil, friends:friends)
}
else{
callback(success: false,errorMsg:error?.localizedDescription,friends:nil)
}
})
My question is how to deserialize the result, and am I correct to assume that the json result is a [Dictionary] and in order for me to change it to a [String] result, should I map it?
If I use the map syntax below I have a few questions
let friends = result?.map {
return $0["uname"] as String?
}
With the optional return value can I get it to return a [String] instead of a [String?] in a graceful way - isn't there a way to let map return only non null values somehow in a concise syntax?
Also is there a better syntax for map that lets me name the parameters instead of using $0?
I use Alamofire with SwiftyJSON to do the job and deserialise array of JSON objects to native objects. Here is an example of how I do it:
Alamofire.request(request)
.responseJSON(options: nil) { (request, response, json, error) -> Void in
if let requestError = error as NSError?{
callback!(nil, nil, requestError)
}
if let httpResponse = response as NSHTTPURLResponse?{
if(httpResponse.statusCode != 200){
callback!(nil, httpResponse, nil)
} else{
let jsonResult = JSON(json!) as JSON
var concerts = self.deserializeConcerts(jsonResult)
callback!(concerts, nil, nil)
}
}
}
Deserialize concerts looks like this:
class private func deserializeConcerts(concerts: SwiftyJSON.JSON) -> [ConcertModel]{
let concertsArray = concerts.object as! NSMutableArray
var concertObjs = [ConcertModel]()
for concert in concertsArray{
let concertObj = ConcertModel(concert: concert as! NSDictionary)
concertObjs.append(concertObj)
}
return concertObjs
}
And finally, ConcertModel initialiser just maps the values of the NSDictionary to the properties of the ConcertModel object.
Updated implementation for Alamofire 4.3
Alamofire.request(url, method: .get)
.responseJSON { response in
if response.data != nil {
let json = JSON(data: response.data!)
let name = json["data"][0]["name"].string
if name != nil {
print(name!)
}
}
}
That will parse this JSON input:
{
data: [
{ name: 'John' },
{ name: 'Dave' }
]
}
Related
I am create simple rest api with swift using swifter library
How i can response json data?
import Swifter
let server = HttpServer()
server["/hello"] = { request in
var userList:[Any] = []
for user in users {
var b : [String: Any] = [:]
b["name"] = user.name
b["id"] = user.id
userList.append(b)
}
return .ok(.json(userList))
}
But there is below error message
Serialization error: invalidObject
I check the library source code, and found the error message reason
...
//Library Source code
func content() -> (Int, ((HttpResponseBodyWriter) throws -> Void)?) {
do {
switch self {
case .json(let object):
guard JSONSerialization.isValidJSONObject(object) else {
throw SerializationError.invalidObject
}
let data = try JSONSerialization.data(withJSONObject: object)
return (data.count, {
try $0.write(data)
})
...
So, I need pass guard JSONSerialization.isValidJSONObject(object) else {
also, there is no enough document for the library, How I can fix this problem ?
Use codable and a jsonEncoder to convert the users array to data and then convert them back to a jsonObject and pass it in:
do {
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(users)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
return .ok(.json(jsonObject))
} catch {
print(error)
return .internalServerError(.htmlBody("\(error.localizedDescription)"))
}
Note that you should consider returning an error to the caller of the service. I've used .internalServerError but you may consider returning a better error.
I'm currently making a migration from Android to iOS, better said Java to Swift, I got a generic response in JSON, but I'm not able to use it as an object and show it in the storyboard. I'm really new to Swift so I've been stuck for a while.
I've tried ObjectMapper and also JSON decode with no result at all.
I declared this response as I used in Java(Android)
class ResponseObjectMapper<T,R>: Mappable where T: Mappable,R:Mappable{
var data:T?
var message:String!
var error:R?
required init?(_ map: Map) {
self.mapping(map: map)
}
func mapping(map: Map) {
data <- map["data"]
message <- map["message"]
error <- map["error"]
}
}
class UserMapper :Mappable{
var email:String?
var fullName:String?
var id:CLong?
var phoneNumber:String?
var token:CLong?
required init?(_ map: Map) {
}
func mapping(map: Map) {
email <- map["email"]
fullName <- map["fullName"]
id <- map["id"]
phoneNumber <- map["phoneNumber"]
token <- map["token"]
phoneNumber <- map["phoneNumber"]
}
}
In my Android project I use the Gson dependency and I was able to use my JSON as an object
class ErrorMapper:Mappable{
var message:String?
var code:Int?
required init?(_ map: Map) {
}
func mapping(map: Map) {
message <- map["message"]
code <- map["code"]
}
}
This is the Alamofire that gave me the JSON.
func login(params: [String:Any]){Alamofire.request
("http://192.168.0.192:8081/SpringBoot/user/login", method: .post,
parameters: params,encoding: JSONEncoding.default, headers:
headers).responseJSON {
response in
switch response.result {
case .success:
let response = Mapper<ResponseObjectMapper<UserMapper,ErrorMapper>>.map(JSONString: response.data)
break
case .failure(let error):
print(error)
}
}
}
If I print the response with print(response) I got
SUCCESS: {
data = {
email = "vpozo#montran.com";
fullName = "Victor Pozo";
id = 6;
phoneNumber = 099963212;
token = 6;
};
error = "<null>";
message = SUCCESS;
}
and if I use this code I can got a result with key and value but I don't know how to use it as an object
if let result = response.result.value {
let responseDict = result as! [String : Any]
print(responseDict["data"])
}
console:
Optional({
email = "vpozo#gmail.com";
fullName = "Victor Pozo";
id = 6;
phoneNumber = 099963212;
token = 6;
})
I would like to use it in an Object, like user.token in a View Controller, probably I'm really confused, trying to map with generic attributes.
Type 'ResponseObjectMapper<UserMapper, ErrorMapper>' does not conform to protocol 'BaseMappable'
First of all you will need a Network Manager which uses Alamofire to make all your requests. I have made generalized one that looks something like this. You can modify it as you want.
import Foundation
import Alamofire
import SwiftyJSON
class NetworkHandler: NSObject {
let publicURLHeaders : HTTPHeaders = [
"Content-type" : "application/json"
]
let privateURLHeaders : HTTPHeaders = [
"Content-type" : "application/json",
"Authorization" : ""
]
enum RequestType {
case publicURL
case privateURL
}
func createNetworkRequestWithJSON(urlString : String , prametres : [String : Any], type : RequestType, completion:#escaping(JSON) -> Void) {
let internetIsReachable = NetworkReachabilityManager()?.isReachable ?? false
if !internetIsReachable {
AlertViewManager.sharedInstance.showAlertFromWindow(title: "", message: "No internet connectivity.")
} else {
switch type {
case .publicURL :
commonRequest(urlString: baseURL+urlString, parameters: prametres, completion: completion, headers: publicURLHeaders)
break
case .privateURL:
commonRequest(urlString: baseURL+urlString, parameters: prametres, completion: completion, headers: privateURLHeaders)
break
}
}
}
func commonRequest(urlString : String, parameters : [String : Any], completion : #escaping (JSON) -> Void , headers : HTTPHeaders){
print("urlString:"+urlString)
print("headers:")
print(headers)
print("parameters:")
print(parameters)
let url = NSURL(string: urlString)
var request = URLRequest(url: url! as URL)
request.httpMethod = "POST"
request.httpHeaders = headers
request.timeoutInterval = 10
let data = try! JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions.prettyPrinted)
let json = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
if let json = json {
print("parameters:")
print(json)
}
request.httpBody = json!.data(using: String.Encoding.utf8.rawValue)
let alamoRequest = AF.request(request as URLRequestConvertible)
alamoRequest.validate(statusCode: 200..<300)
alamoRequest.responseJSON{ response in
print(response.response?.statusCode as Any )
if let status = response.response?.statusCode {
switch(status){
case 201:
print("example success")
SwiftLoader.hide()
case 200 :
if let json = response.value {
let jsonObject = JSON(json)
completion(jsonObject)
}
default:
SwiftLoader.hide()
print("error with response status: \(status)")
}
}else{
let jsonObject = JSON()
completion(jsonObject)
SwiftLoader.hide()
}
}
}
}
After this when ever you need to make a request you can use this function. This will take in parameters if any needed and once the request is complete it will execute a call back function in which you can handle the response. The response here will be of SWIFTYJSON format.
func makeNetworkRequest(){
let networkHandler = NetworkHandler()
var parameters : [String:String] = [:]
parameters["email"] = usernameTextField.text
parameters["pass"] = passwordTextField.text
networkHandler.createNetworkRequestWithJSON(urlString: "http://192.168.0.192:8081/SpringBoot/user/login", prametres: parameters, type: .publicURL, completion: self.handleResponseForRequest)
}
func handleResponseForRequest(response: JSON){
if let message = response["message"].string{
if message == "SUCCESS"{
if let email = response["data"]["email"].string{
//Do something with email.
}
if let fullName = response["data"]["fullName"].string{
//Do something with fullName.
}
if let id = response["data"]["id"].int{
//Do something with id.
}
if let phoneNumber = response["data"]["phoneNumber"].int64{
//Do something with phoneNumber.
}
if let token = response["data"]["token"].int{
//Do something with token.
}
}else{
//Error
}
}
}
Hope this helps. Let me know if you get stuck anywhere.
Attempting to update a menu item to return all fixtures from api.
I've got a list of fixtures being returned.
How do I go about updating the fixtureMenuItem in the MenuController with all fixtures returned from the JSON? I thought I might be able to do something along the lines of fixtureMenuItem.title = fixtures.description
, but I'm getting "Thread 1: Fatal error: Index out of range."
Model
struct LiveScores: Codable {
let success: Bool
let fixturesData: FixturesData?
enum CodingKeys: String, CodingKey {
case fixturesData = "data"
case success
}
}
struct FixturesData: Codable {
let fixtures: [Fixture]
let nextPage, prevPage: Bool
enum CodingKeys: String, CodingKey {
case fixtures
case nextPage = "next_page"
case prevPage = "prev_page"
}
}
struct Fixture: Codable, CustomStringConvertible {
let id, date, time, round: String
let homeName, awayName, location, leagueID: String
let homeID, awayID: Int?
enum CodingKeys: String, CodingKey {
case id, date, time, round
case homeName = "home_name"
case awayName = "away_name"
case location
case leagueID = "league_id"
case homeID = "home_id"
case awayID = "away_id"
}
var description: String {
return "\(time): \(homeName) vs. \(awayName)"
}
}
// MARK: Convenience initializers
extension LiveScores {
init(data: Data) throws {
self = try JSONDecoder().decode(LiveScores.self, from: data)
}
}
Menu Controller - this is where I want to update the fixture menu item, to include the time, home and away team names. "Here is where all the fixtures will be populated!" - this is the hardcoded text I wish to replace with the fixture data.
var fixtures = [Fixture]()
func updateScores() {
liveScoreApi.fetchFixtures()
if let fixtureMenuItem = self.Menu.item(withTitle: "Fixtures") {
fixtureMenuItem.title = "Here is where all the fixtures will be populated!"
// TODO - populate the UI with fixtures returned from JSON response
}
}
Fetch Fixtures - here's where the fixtures are retrieved.
func fetchFixtures() {
let session = URLSession.shared
let url = URL(string: "\(baseUrl)fixtures/matches.json?key=\
(apiKey)&secret=\(apiSecret)&date=2018-06-02")
let task = session.dataTask(with: url!) { data, response, err in
// check for a hard error
if let error = err {
NSLog("Live Scores Api Error: \(error)")
}
// check the response code
if let httpResponse = response as? HTTPURLResponse {
switch httpResponse.statusCode {
case 200: // perfecto!
if let liveScores = try? LiveScores.init(data: data!),
let fixture = liveScores.fixturesData
{
NSLog("\(fixture)")
}
case 401: // unauthorised
NSLog("Live Score Api returned an 'unauthorised' response.")
default:
NSLog("Live Scores Api returned response: %d %#", httpResponse.statusCode, HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))
}
}
}
task.resume()
}
In this example fixture data there are 26 fixtures and I want to show all of these.
Variations of this question come up constantly on SO.
Async functions don't wait for their results to be available. You give them a callback, which is a closure (a block of code you provide) that gets executed once the operation is complete.
You should rewrite your fetchFixtures() function to take a completion handler, and then refactor your updateScores() function to pass the code that updates your menu item into the completion handler for FetchFixtures.
See my answer to the question in the thread below for a simple example of this approach:
Swift: Wait for Firebase to load before return a function
As Duncan said in his answer, the issue was that the results weren't actually available.
I've implemented a completion handler of handleCompletion: on the fetchFixtures() function, which takes a true/false value plus the fixtures data. This is then returned in each http response case as shown below:
func fetchFixtures(handleCompletion:#escaping (_ isOK:Bool,_ param:
FixturesData?)->()) {
let session = URLSession.shared
let url = URL(string: "\(baseUrl)fixtures/matches.json?key=\
(apiKey)&secret=\(apiSecret)&date=2018-06-04")
let task = session.dataTask(with: url!) { data, response, err in
// check for a hard error
if let error = err {
NSLog("Live Scores Api Error: \(error)")
}
// check the response code
if let httpResponse = response as? HTTPURLResponse {
switch httpResponse.statusCode {
case 200: // perfecto!
if let liveScores = try? LiveScores.init(data: data!),
let fixture = liveScores.fixturesData
{
//NSLog("\(fixture)")
handleCompletion(true, fixture)
}
case 401: // unauthorised
NSLog("Live Score Api returned an 'unauthorised' response.")
handleCompletion(false, nil)
default:
NSLog("Live Scores Api returned response: %d %#", httpResponse.statusCode, HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))
handleCompletion(false, nil)
}
}
}
task.resume()
}
After implementing the above, I refactored the updateScores() to use this completion handler.
func updateScores() {
liveScoreApi.fetchFixtures() { (
isOK, fixture) in
if isOK == true {
if let fixtureMenuItem = self.Menu.item(withTitle: "Fixtures") {
fixtureMenuItem.title = (fixture?.fixtures.description)!
}
}
else {
NSLog("error fetching!")
}
}
}
The fixtureMenuItem now successfully displays the data if available.
So I'm trying to get his raw json data and use it to ultimately be viewed in a table(so one table cell would be --> Emirates - $1588.77)
Problem: Having trouble parsing the JSON data.. alamofire apparently does it automatically? but im completely confused with the data types. I keep getting weird errors like 'doesnt have a member named subscript" (I've also got swiftyjson installed but aa non-swiftyjson solution should work as well.
Code:
request(qpxRequest).responseJSON { (request, response, json, error) -> Void in
if response != nil {
//println(response!)
}
if json != nil {
// 1. parse the JSON data into a Foundation object
// 2. Grab the data from the foundation object (so its can be looped though in a table)
}
{
trips = {
data = {
carrier = (
{
name = "Cathay Pacific Airways Ltd.";
},
{
name = Emirates;
},
{
name = "Ethiopian Airlines Enterprise";
},
{
name = "Qantas Airways Ltd.";
},
{
name = "South African Airways";
}
);
};
tripOption = (
{
saleTotal = "AUD1537.22";
},
{
saleTotal = "AUD1588.77";
},
{
saleTotal = "AUD1857.42";
},
{
saleTotal = "AUD1857.42";
},
{
saleTotal = "AUD1922.42";
}
);
};
}
-------- Edit.
Using this model.
class FlightDataModel {
var carrier: String
var price: String
init(carrier: String?, price: String?) {
self.carrier = carrier!
self.price = price!
}
}
How woudl I use your solution to add it to an array of FlightDataModel class
This my my attempt..
var arrayOfFlights : [FlightDataModel] = [FlightDataModel]()
if let tripOptions = trips["tripOption"] as? [[String:AnyObject]] {
for (index, tripOption) in enumerate(tripOptions) {
//println("\(index): " + (tripOption["saleTotal"]! as String))
self.arrayOfFlights[index].carrier = tripOption["saleTotal"]! as String
println("\(self.arrayOfFlights[index].carrier)")
}
Alamofire can do it, but you have to dig into your JSON structure. :)
Like this, using Alamofire's responseJSON method:
Alamofire.request(.GET, YOUR_URL, parameters: nil, encoding: .URL).responseJSON(options: NSJSONReadingOptions.allZeros) { (request, response, json, error) -> Void in
if let myJSON = json as? [String:AnyObject] {
if let trips = myJSON["trips"] as? [String:AnyObject] {
if let data = trips["data"] as? [String:AnyObject] {
if let carriers = data["carrier"] as? [[String:String]] {
for (index, carrierName) in enumerate(carriers) {
println("\(index): " + carrierName["name"]!)
}
}
}
if let tripOptions = trips["tripOption"] as? [[String:AnyObject]] {
for (index, tripOption) in enumerate(tripOptions) {
println("\(index): " + (tripOption["saleTotal"]! as! String))
}
}
}
}
}
Output:
0: Cathay Pacific Airways Ltd.
1: Emirates
...
0: AUD1537.22
1: AUD1588.77
...
It's a bit easier with SwiftyJSON indeed. And for diversity's sake, we'll use Alamofire's responseString method this time:
Alamofire.request(.GET, YOUR_URL, parameters: nil, encoding: .URL).responseString(encoding: NSUTF8StringEncoding, completionHandler: {(request: NSURLRequest, response: NSHTTPURLResponse?, responseBody: String?, error: NSError?) -> Void in
if let dataFromString = responseBody!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
let carriers = json["trips"]["data"]["carrier"].array
for (index, carrier) in enumerate(carriers!) {
println("\(index):" + carrier["name"].string!)
}
let tripOption = json["trips"]["tripOption"].array
for (index, option) in enumerate(tripOption!) {
println("\(index):" + option["saleTotal"].string!)
}
}
})
Output:
0: Cathay Pacific Airways Ltd.
1: Emirates
...
0: AUD1537.22
1: AUD1588.77
...
Note: I've used enumerate as an example for how getting the index of the content at the same time you get the content.
This might be an easy question, but how can I create a simple JSON String from an existing Array?
In the Documentation the only thing I found is:
class func JSONObjectWithData(_ data: NSData,
options opt: NSJSONReadingOptions,
error error: NSErrorPointer) -> AnyObject?
But I only want to create a simple JSON String from an existing Array.
With the existing Swift JSON Extensions I am only able to parse existing JSON Strings, and could not create a new String.
I could manually create the String, but there might be a better solution.
Any help would be greatly appreciated.
If you have a NSArray object, you could create your JSON String by using something like:
func JSONStringify(value: AnyObject, prettyPrinted: Bool = false) -> String {
var options = prettyPrinted ? NSJSONWritingOptions.PrettyPrinted : nil
if NSJSONSerialization.isValidJSONObject(value) {
if let data = NSJSONSerialization.dataWithJSONObject(value, options: options, error: nil) {
if let string = NSString(data: data, encoding: NSUTF8StringEncoding) {
return string
}
}
}
return ""
}
I found here the details for this function.
SWIFT 3
func JSON2String(jsonObj: AnyObject, prettyPrinted: Bool = false) -> String {
let options = prettyPrinted ? JSONSerialization.WritingOptions.prettyPrinted : []
if JSONSerialization.isValidJSONObject(jsonObj) {
if let data = try? JSONSerialization.data(withJSONObject: jsonObj, options: options) {
if let string = String(data: data, encoding: Task.StringEncoding) {
return string
}
}
}
return ""
}
Let's say you have an array of Strings called array. You would do this:
let array = [ "String1", "String2" ]
var error:NSError?
let data = NSJSONSerialization.dataWithJSONObject(array, options: NSJSONWritingOptions.allZeros, error: &error)
let str = NSString(data:data!, encoding:NSUTF8StringEncoding)
println("\(str!)")