How to parse JSON dictionary using SwiftyJSON and Alamofire - ios

I have a problem with my alamofire.request. I tried to to decode JSON response with Struct using SwiftyJSON.
. But my model data is getting nil.
here is my API response
{
"userDetails" :
{ "id":2,
"roleID":1,
"successFlag":1
},
"settingID" : "20"
}
Model class
import Foundation
import SwiftyJSON
struct User {
var settingID : String?
var userdetails : UserDetails?
init(json : JSON?) {
self.settingID = json?["settingID"].string
if let value = json?["userDetails"].dictionaryObject {
let new = UserDetails(json: JSON(value))
self.userdetails = new
}
}
}
struct UserDetails {
var id : Int?
var roleID : Int?
var successFlag : Int?
init(json : JSON?) {
self.id = json?["id"].int
self.roleID = json?["roleID"].int
self.successFlag = json?["successFlag"].int
}
}
My code for Data fetching using Alamofire and SwiftyJSON
import Alamofire
import SwiftyJSON
var userData : [User] = []
func fetchData() {
Alamofire.request(base_url + APIManager.loginApi, method: .post, parameters: params, encoding: URLEncoding.queryString, headers: nil).responseJSON { (resp) in
switch resp.result {
case .success :
print(resp.result)
do {
let myResult = try JSON(data: resp.data!)
print(myResult)
myResult.dictionaryValue.forEach({(user) in
let newUser = User(json: JSON(user))
self.userData.append(newUser)
print(self.userData)
})
}catch {
print(error)
}
break
case .failure :
break
}
}
}
But if i print self.userData , getting nill response.
Have you any idea why I can't decode my struct with my JSON data?.
Thanks a lot for your help

Try using Codable instead. It is easier to create a Codable model and is Apple recommended.
struct Root: Decodable {
let userDetails: User
let settingID: String
}
struct User: Decodable {
let id: Int
let roleID: Int
let successFlag: Int
}
Parse the data like,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}

Change your response code like below
switch response.result {
case .success(let value):
let response = JSON(value)
print("Response JSON: \(response)")
let newUser = User(json: response)
self.userData.append(newUser)
print(self.userData)
case .failure(let error):
print(error)
break
}

Related

How to print error message while parsing in swift

this is json error structure:
{
"jsonrpc": "2.0",
"error": {
"code": "-32700",
"message": "Parse error",
"meaning": "Could not decode token: Error while decoding to JSON: Syntax error"
}
}
this is success response structure:
{
"jsonrpc": "2.0",
"result": {
"users": [
{
"id": 371,..... so on data
here am unable to print error > message in my code
code: here able to print json response result or error dictionary in case .success(_) print statement but how to print only error > meaning
how to print error's "Parse error" in below code, please guide me
class GeneralResponse<T: Codable>: Codable {
var result: T?
let error: ErrorClass?
}
struct ErrorClass: Codable {
let code, message, meaning: String?
}
struct RequestObject {
var params: [String: AnyObject]? = nil
var method: HTTPMethod
var path: String
var isTokenNeed: Bool = false
var vc: UIViewController?
}
class NetworkManager {
private let decoder: JSONDecoder
static let sharedInstance = NetworkManager()
public init(_ decoder: JSONDecoder = JSONDecoder()) {
self.decoder = decoder
}
public func serviceCall<T: Codable>(_ objectType: T.Type,
with request: RequestObject,
completion: #escaping (T?, Error?) -> Void) {
let paramsDict = ["jsonrpc" : "2.0", "params" : request.params ?? nil] as [String : Any?]
AF.request(request.path, method: request.method, parameters: paramsDict as [String : AnyObject], encoding: JSONEncoding.default, headers: "Accept": "application/json")
.responseJSON { response in
switch response.result {
case .success(_):
do {
print("only json respopnse \(response)")
let data = response.data
let responseData = try self.decoder.decode(T.self, from: data ?? Data())
completion(responseData, nil)
} catch {
completion(nil, error)
print("in catch \(error)")
}
case .failure(let AFError):
let error = AFError
print(error.localizedDescription)
print("failure error: \(error)")
}
}
}
}
o/p for error in above call with print("only json respopnse \(response)")
only json respopnse success({
error = {
code = "-32700";
meaning = "Token Signature could not be verified.";
message = "Parse error";
};
jsonrpc = "2.0";
})
EDIT 2: here success response not coming in viewcontroller
struct PostModel: Codable {
let jsonrpc: String?
let result: PostResult?
}
struct PostResult: Codable {
let users: [PostUser]?
}
struct PostUser: Codable {
let id: Int?
}
and calling like this in vc
var k12Data: PostModel?
let paramet = ["location": "", "country": "", "gender": "", "keyword": ""] as [String : AnyObject]
let request = RequestObject(params: paramet, method: .post, path: "https://phpwebdevelopmentserv/", isTokenNeed: true, vc: self)
NetworkManager.sharedInstance.serviceCall(PostModel.self, with: request) { (response, error) in
print("viewcontroller data \(response)")
}
o/p
viewcontroller data Optional(TestigAllInOne.PostModel(jsonrpc: nil, result: nil))
You didn´t show us your Postmodel so I am asuming it is of some type of GeneralResponse<T> class.
You should decode to your GeneralResponse class:
let responseData = try self.decoder.decode(GeneralResponse<T>.self, from: data)
now you can print your error if one exists:
print(responseData.error?.meaning ?? "no error")
and call your completion handler like this:
guard let result = responseData.result else{
// unknown error has occured
completion(nil, nil) // you probably need to create a custom error here
return
}
completion(result, nil)
the main essence is to not pass GeneralResponse as argument to your function but the result type you are expecting. So if for example your User type looks like this:
struct User: Codable{
var id: String
}
call it like this:
networkmanager.serviceCall([User].self, with: RequestObject(...)) { users, error in
// users is of type `[User]` here.
}

How to convert '__NSIArrayI' to JSON Array in Swift?

I am new to Swift 4 and working on an iOS app. I have the following JSON data retrieved from the server which will be extracted to data table.
[
{
"fund_nav_validity":"24 August to 31 August 2016\n",
"fund_nav_cost":10,
"fund_nav_sell":9.85,
"nav_id":118,
"fund_nav_buy":10,
"fund_id":1,
"nav_as_on_date":"24-Aug-16",
"fund_nav_market":9.95
},
{
"fund_nav_validity":"04 September to 07 September 2016\n",
"fund_nav_cost":10,
"fund_nav_sell":9.85,
"nav_id":117,
"fund_nav_buy":10,
"fund_id":1,
"nav_as_on_date":"01-Sep-16",
"fund_nav_market":9.95
}
]
I have done following task in the navdata function to retrieve the '__NSIArrayI' to JSON:
func getNAVData(){
Alamofire.request(url, method: .post).responseJSON{
response in
if response.result.isSuccess{
print("Success")
//let navDataJSON : JSON = JSON(response.result.value!
let navDataJSON : JSON = JSON(response.result.value!)
print(navDataJSON)
}else{
print("Failed")
}
}
}
I need to convert it to JSON Array which is done in java. How to do the similar task in Swift 4?
you are in the right way, in here you need to convert your JSON response to array the reason your json started with this type[{}] and check your JSON response contains the value or not in intially. try the below code
if response.result.isSuccess{
print("Success")
// convert your JSON to swiftyJSON array type if your JSON response as array as well as check its contains data or not
guard let resJson = JSON(responseObject.result.value!).array , !resJson.isEmpty else { return
}
// create the Swifty JSON array for global access like wise
var navDataJSON = [JSON]() //[JSON]() --> Swifty-JSON array memory allocation.
navDataJSON = resJson
}
You're getting the JSON array and if you want to convert to model Array then you can use create Model Struct and confirm to Decodable Protocol, without using SwiftyJSON using inbuilt solution to parse JSON.
import Foundation
// MARK: - Element
struct Model: Decodable {
let fundNavValidity: String
let fundNavCost: Int
let fundNavSell: Double
let navId: Int
let fundNavBuy: Int
let fundId: Int
let navAsOnDate: String
let fundNavMarket: Double
enum CodingKeys: String, CodingKey {
case fundNavValidity = "fund_nav_validity"
case fundNavCost = "fund_nav_cost"
case fundNavSell = "fund_nav_sell"
case navId = "nav_id"
case fundNavBuy = "fund_nav_buy"
case fundId = "fund_id"
case navAsOnDate = "nav_as_on_date"
case fundNavMarket = "fund_nav_market"
}
}
then in getNAVData method you can use JSONDecoder to covert to Model struct array like below
func getNAVData() {
Alamofire.request(url, method: .post).responseJSON {
response in
switch response.result {
case .success(let data):
do {
let modelArray = try JSONDecoder().decode([Model].self, from: data)
print(modelArray)
} catch {
print("Error: \(error)")
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
If you want to use SwiftyJSON return responseData rather than responseJSON. This avoids a double conversion, and handle always a potential error
func getNAVData(){
Alamofire.request(url, method: .post).responseData { response in
switch response.result {
case .success(let data):
let navDataJSON = JSON(data).arrayValue
print(navDataJSON)
case .failure(let error): print(error)
}
}
}
The result navDataJSON is a JSON array.
However in Swift 4+ it's highly recommended to use the more efficient and built-in Codable protocol
struct Fund : Decodable {
let fundNavValidity, navAsOnDate : String
let fundNavCost, navId, fundNavBuy, fundId : Int
let fundNavSell, fundNavMarket : Double
}
func getNAVData(){
Alamofire.request(url, method: .post).responseData { response in
switch response.result {
case .success(let data):
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let navDataJSON = decoder.decode([Fund].self, from : data)
print(navDataJSON)
} catch { print(error) }
case .failure(let error): print(error)
}
}
}
The result is an array of Fund structs.

Alamofire parse JSON msg and status

I want to display the msg on UITextView using the status, but it display "Success: 200":
{ "status" : 500, "msg" : "\"Information is invalid\"" }
I'm using Swift4 in XCode10, here is my code:
import UIKit
import Alamofire
import SwiftyJSON
class VerifyAccount: UIViewController {
let verify_url = "http://192.168.43.222:3000/mobile/account_verification"
#IBOutlet weak var msgHandler: UITextView!
func postData(url: String , parameters: [String : String]) {
Alamofire.request(url, method: .post, parameters: parameters).responseJSON {
response in
if response.result.isSuccess {
let postJSON : JSON = JSON(response.result.value!)
print(postJSON)
if let status = response.response?.statusCode {
switch(status) {
case 200:
self.msgHandler.text = ("Success: \(status)")
case 500:
self.msgHandler.text = ("Invalid: \(status)")
default:
}
}
} else {
print("Error: \(String(describing: response.result.error))")
}
}
}
#IBAction func verifyBtn(_ sender: Any) {
let compare : [String : String] = ["id" : id , "fname" : fname , "lname" : lname]
postData(url: verify_url , parameters : compare)
}
}
You need to change the way how you handling your response. I've updated your code, it should work.
import UIKit
import Alamofire
import SwiftyJSON
class VerifyAccount: UIViewController {
let verify_url = "http://192.168.43.222:3000/mobile/account_verification"
#IBOutlet weak var msgHandler: UITextView!
func postData(url: String , parameters: [String : String]) {
Alamofire.request(url, method: .post, parameters: parameters).responseJSON {
response in
switch response.result {
case .success:
print("server response: \(response.value!)")
do {
if var json = try JSONSerialization.jsonObject(with: response.data!, options: []) as? [String:Any] {
// Get value by key
var message = "";
if let msg = json["msg"] as? String
{
message = msg; // your message is here, you can do anything with it, whatever you want.
}
var status = 0;
if let st = json["status"] as? Int
{
status = st;
}
}
} catch let error as NSError {
}
break
case .failure(let error):
self.delegate.APIOnError(requestCode: requestCode);
print("server error: \(error.localizedDescription)")
}
}
}
You can get a value safely (whether it's a String, Int, etc...) from your JSON object like so:
let status = postJSON["status"].int ?? 0
let message = postJSON["msg"].string ?? "No Message"
The (??) double question mark gives you the ability to provide a default value in case your parsing fails to get an Int or String
Parse your response data like
guard data _ = response.result.value as? [String: Any] else {
print("Error: \(String(describing: response.result.error))")
return
}
let message = data["msg"] as? String ?? "Unable to parse message"
if let statusCode = data["status"] as? Int {
switch statusCode {
case 200: self.msgHandler.text = ("Success: \(message)")
case 500: self.msgHandler.text = ("Invalid: \(message)")
default:self.msgHandler.text = ("Invalid status code")
}
}
You were using wrong data to parse. You can try this:
if let status = response.response?.statusCode, let message = postJSON['msg'] {
switch(status) {
case 200:
self.msgHandler.text = ("Success: \(message)")
case 500:
self.msgHandler.text = ("Invalid: \(message)")
default:
}
}

Alamofire response object mapping

I am an android developer new to swift 3 programming, I am using Alamofire for making api calls and to avoid tedious json paring I am using AlamofireObjectMapper library.
I have a ApiController which has a function to make api calls below is the code for that:
public static func makePostRequest<T: Mappable>(url: String, params: Parameters, networkProtocol: NetworkProtocol, responseClass: T){
let headers = getHeaders()
networkProtocol.showProgress()
Alamofire.request(url, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers)
.validate()
.responseData{ response in
let json = response.result.value
var jsonString = String(data: json!, encoding: String.Encoding.utf8)
let responseObject = responseClass(JSONString: jsonString!)
switch(response.result){
case .success(_):
networkProtocol.hideProgress()
networkProtocol.onResponse(response: response)
break
case .failure(_):
networkProtocol.hideProgress()
networkProtocol.onErrorResponse(response: response)
break
}
}
The Json response template I am getting from server is:
{
"some_int": 10,
"some_array":[...]
}
Below is my model class:
import ObjectMapper
class BaseResponse: Mappable {
var some_int: Int?
var some_array: [Array_of_objects]?
required init?(map: Map) {
some_int <- map["some_int"]
some_array <- map["some_array"]
}
func mapping(map: Map) {
}
}
And below is the function to class make the api call:
public static func callSomeApi(params: Parameters, networkProtocol: NetworkProtocol){
ApiHelper.makePostRequest(url: AppConstants.URLs.API_NAME, params: params, networkProtocol: networkProtocol, responseClass: BaseResponse)
}
Now the error is in the below line
let responseObject = responseClass(JSONString: jsonString!)
I am not able to understand how to convert jsonString into the responseClass generic object which I am accepting from View controller
Someone please help me resolve this, stuck on this issue for quite a while now.
You can use AlamofireMapper:
With json:
{
"page":1,
"per_page":3,
"total":12,
"total_pages":4,
"data":[
{
"id":1,
"first_name":"George",
"last_name":"Bluth",
"avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"
},
{
"id":2,
"first_name":"Janet",
"last_name":"Weaver",
"avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
},
{
"id":3,
"first_name":"Emma",
"last_name":"Wong",
"avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/olegpogodaev/128.jpg"
}
]
}
Swift class:
class UserResponse: Decodable {
var page: Int!
var per_page: Int!
var total: Int!
var total_pages: Int!
var data: [User]?
}
class User: Decodable {
var id: Double!
var first_name: String!
var last_name: String!
var avatar: String!
}
Request with alamofire
let url1 = "https://raw.githubusercontent.com/sua8051/AlamofireMapper/master/user1.json"
Alamofire.request(url1, method: .get
, parameters: nil, encoding: URLEncoding.default, headers: nil).responseObject { (response: DataResponse<UserResponse>) in
switch response.result {
case let .success(data):
dump(data)
case let .failure(error):
dump(error)
}
}
Link: https://github.com/sua8051/AlamofireMapper
Generic Response Object Serialization using Swift 4 Codable
If you don't want to use another dependency like ObjectMapper you can do the following way but you may have to make some chagnes.
Following is a typical model which we use to deserialize JSON data with generics using Alamofire. There is plenty of examples and excellent documentation on Alamofire.
struct User: ResponseObjectSerializable, ResponseCollectionSerializable, CustomStringConvertible {
let username: String
let name: String
var description: String {
return "User: { username: \(username), name: \(name) }"
}
init?(response: HTTPURLResponse, representation: Any) {
guard
let username = response.url?.lastPathComponent,
let representation = representation as? [String: Any],
let name = representation["name"] as? String
else { return nil }
self.username = username
self.name = name
}
}
Using Codable protocol introduced in Swift 4
typealias Codable = Decodable & Encodable
The first step in this direction is to add helper functions that
will do half of the work in deserialization JSON data and handle
errors. Using Swift extensions we add functions to decode incoming
JSON into our model struct/class that we will write afterward.
let decoder = JSONDecoder()
let responseObject = try? decoder.decode(T.self, from: jsonData)
The decoder (1) is an object that decodes instances of a data type from JSON objects.
Helper functions
extension DataRequest{
/// #Returns - DataRequest
/// completionHandler handles JSON Object T
#discardableResult func responseObject<T: Decodable> (
queue: DispatchQueue? = nil ,
completionHandler: #escaping (DataResponse<T>) -> Void ) -> Self{
let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
guard error == nil else {return .failure(BackendError.network(error: error!))}
let result = DataRequest.serializeResponseData(response: response, data: data, error: error)
guard case let .success(jsonData) = result else{
return .failure(BackendError.jsonSerialization(error: result.error!))
}
// (1)- Json Decoder. Decodes the data object into expected type T
// throws error when failes
let decoder = JSONDecoder()
guard let responseObject = try? decoder.decode(T.self, from: jsonData)else{
return .failure(BackendError.objectSerialization(reason: "JSON object could not be serialized \(String(data: jsonData, encoding: .utf8)!)"))
}
return .success(responseObject)
}
return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}
/// #Returns - DataRequest
/// completionHandler handles JSON Array [T]
#discardableResult func responseCollection<T: Decodable>(
queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<[T]>) -> Void
) -> Self{
let responseSerializer = DataResponseSerializer<[T]>{ request, response, data, error in
guard error == nil else {return .failure(BackendError.network(error: error!))}
let result = DataRequest.serializeResponseData(response: response, data: data, error: error)
guard case let .success(jsonData) = result else{
return .failure(BackendError.jsonSerialization(error: result.error!))
}
let decoder = JSONDecoder()
guard let responseArray = try? decoder.decode([T].self, from: jsonData)else{
return .failure(BackendError.objectSerialization(reason: "JSON array could not be serialized \(String(data: jsonData, encoding: .utf8)!)"))
}
return .success(responseArray)
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
Second, I earlier mentioned “using Swift 4 Codable” but if all we
want is to decode JSON from the server, we only need is a model
struct/class that conforms to protocol Decodable. (If you have the
same structure you want to upload you can use Codable to handle both
decoding and encoding) So, now our User model struct now looks like
this.
struct User: Decodable, CustomStringConvertible {
let username: String
let name: String
/// This is the key part
/// If parameters and variable name differ
/// you can specify custom key for mapping "eg. 'user_name'"
enum CodingKeys: String, CodingKey {
case username = "user_name"
case name
}
var description: String {
return "User: { username: \(username), name: \(name) }"
}
}
Lastly, our function call to API looks like.
Alamofire.request(Router.readUser("mattt"))).responseObject{ (response: DataResponse<User>) in
// Process userResponse, of type DataResponse<User>:
if let user = response.value {
print("User: { username: \(user.username), name: \(user.name) }")
}
}
For more complex (nested) JSON, the logic remains the same and only modifications you need in model struct/class is that all structs/classes must conform to Decodable protocol and Swift takes care of everything else.
You can use SwiftyJSON: https://cocoapods.org/pods/SwiftyJSON
Here is some example code could help you:
Alamofire.request(endpointURL, method: .get, parameters: params, encoding: URLEncoding.default, headers: nil).validate().responseJSON()
{
(response) in
if response.result.isFailure
{
print("ERROR! Reverse geocoding failed!")
}
else if let value = response.result.value
{
var country: String? = nil
var county: String? = nil
var city: String? = nil
var town: String? = nil
var village: String? = nil
print("data: \(value)")
let json = JSON(value)
print("json: \(json)")
country = json["address"]["country"].string
county = json["address"]["county"].string
city = json["address"]["city"].string
town = json["address"]["town"].string
village = json["address"]["village"].string
}
else
{
print("Cannot get response result value!")
}
}
Please let you know the code has been simplified (lot of line has been removed) and pasted here from my actual project, so this code is not tested, maybe contains typos or something like that, but the logic is visible
For Object Mapping you need follow this with AlamofireObjectMapper .
//Declare this before ViewLoad
var BaseResponse: Array<BaseResponse>?
// After you receive response from API lets say "data"
if let jsonData = data as? String {
self.BaseResponse = Mapper< BaseResponse >().mapArray(JSONString: jsonData)
}

Handling XML data with Alamofire in Swift

I started to use cocoapods with my current ios project. I need to use SOAP to get content with easy way for my ios project. I have googled it and Alamofire pod is great for me. Because I am using Swift programming language.
I have inited easily this pod. But my web services return me XML result. And I want to serialisation to array this XML result. But I can't.
When I call my web service with a browser I get this kind of result
Alamofire response method is like this:
Alamofire.request(.GET, "http://my-web-service-domain.com", parameters: nil)
.response { (request, response, data, error) in
println(request)
println(response)
println(error)
}
When I run this method I see this output on the terminal:
<NSMutableURLRequest: 0x170010a30> { URL: http://my-web-service-domain.com }
Optional(<NSHTTPURLResponse: 0x1704276c0> { URL: http://my-web-service-domain.com } { status code: 200, headers {
"Cache-Control" = "private, max-age=0";
"Content-Length" = 1020;
"Content-Type" = "text/xml; charset=utf-8";
Date = "Thu, 18 Jun 2015 10:57:07 GMT";
Server = "Microsoft-IIS/7.5";
"X-AspNet-Version" = "2.0.50727";
"X-Powered-By" = "ASP.NET";
} })
nil
I want to get result to an array which see on browser to show my storyboard.
Can anybody help me how to serialise this data with Alamofire framework or Swift language?
If I did not misunderstand your description, I think you would like to get the XML data and parse it, right? Regarding to this, you may handle with wrong variables in the response callback. You should println(data) to check the XML document.
For parsing XML data, you could consider SWXMLHash. The Alamofire request could look like:
Alamofire.request(.GET, "http://my-web-service-domain.com", parameters: nil)
.response { (request, response, data, error) in
println(data) // if you want to check XML data in debug window.
var xml = SWXMLHash.parse(data!)
println(xml["UserDTO"]["FilmID"].element?.text) // output the FilmID element.
}
Further information about XML management, please check SWXMLHash.
Alamofire 4.x - Swift 3.x:
(please note that in this example I've used URLEncoding.default instead of URLEncoding.xml because the xml parameter exclude the possibility to pass parameters and headers, so default is more confortable.)
let url = "https://httpbin.org/get"
let parameters: Parameters = ["foo": "bar"]
let headers: HTTPHeaders = [
"Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
"Accept": "application/json"
]
Alamofire.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers)
.responseString { response in
print(" - API url: \(String(describing: response.request!))") // original url request
var statusCode = response.response?.statusCode
switch response.result {
case .success:
print("status code is: \(String(describing: statusCode))")
if let string = response.result.value {
print("XML: \(string)")
}
case .failure(let error):
statusCode = error._code // statusCode private
print("status code is: \(String(describing: statusCode))")
print(error)
}
}
Alamofire 3.0 october 2015 and Xcode 7 according to the 3.0.0-beta.3 README and the Alamofire 3.0 Migration Guide.
For me the correct syntax is:
Alamofire.request(.GET, url, parameters: params, encoding: ParameterEncoding.URL).responsePropertyList { response in
if let error = response.result.error {
print("Error: \(error)")
// parsing the data to an array
} else if let array = response.result.value as? [[String: String]] {
if array.isEmpty {
print("No data")
} else {
//Do whatever you want to do with the array here
}
}
}
If you want a good XML parser, please take a look to SWXMLHash.
An example could be:
let xml = SWXMLHash.parse(string)
Using Alamofire 3.0 current version as of Sept 2015 and Xcode 7.
The implementation bellow has the advantage of not using an additional external library such as SWXMLHash
Alamofire.request(.GET, urlString, encoding: .PropertyList(.XMLFormat_v1_0, 0)).responsePropertyList { request, response, result in
//Note that result have two properties: error and value as of Alamofire 3.0, check the migration guide for more info
if let error = result.error {
print("Error: \(error)")
// parsing the data to an array
} else if let array = result.value as? [[String: String]] {
if array.isEmpty {
print("No data")
} else {
//Do whatever you want to do with the array here
}
}
}
If you want to map the XML to swift objects, you may also consider XMLMapper. (uses the same technique as the ObjectMapper)
Create your model by implementing XMLMappable protocol:
class UserDTO: XMLMappable {
var nodeName: String!
var extensionData: String?
var canChangeDeviceConfig: BooleanAtttribute?
var canChangeDriverConfig: BooleanAtttribute?
var canChangeFleetConfig: BooleanAtttribute?
var canChangeGeofenceConfig: BooleanAtttribute?
var canSaveHistory: BooleanAtttribute?
var canViewReport: BooleanAtttribute?
var canWatchHistory: BooleanAtttribute?
var deliverDailyReportByEmail: BooleanAtttribute?
var deliverDailyReportBySms: BooleanAtttribute?
var email: String?
var firm: String?
var firmId: Int?
var firstName: String?
var id: Int?
var isActive: Bool?
var isAdmin: Bool?
var lastName: String?
var phone: String?
var recivesDailyReport: BooleanAtttribute?
var userName: String?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
extensionData <- map["ExtensionData"]
canChangeDeviceConfig <- map["CanChangeDeviceConfig"]
canChangeDriverConfig <- map["CanChangeDriverConfig"]
canChangeFleetConfig <- map["CanChangeFleetConfig"]
canChangeGeofenceConfig <- map["CanChangeGeofenceConfig"]
canSaveHistory <- map["CanSaveHistory"]
canViewReport <- map["CanViewReport"]
canWatchHistory <- map["CanWatchHistory"]
deliverDailyReportByEmail <- map["DeliverDailyReportByEmail"]
deliverDailyReportBySms <- map["DeliverDailyReportBySms"]
email <- map["Email"]
firm <- map["Firm"]
firmId <- map["FirmId"]
firstName <- map["FirstName"]
id <- map["Id"]
isActive <- (map["IsActive"], BooleanTransformeType(trueValue: "true", falseValue: "false"))
isAdmin <- (map["IsAdmin"], BooleanTransformeType(trueValue: "true", falseValue: "false"))
lastName <- map["LastName"]
phone <- map["Phone"]
recivesDailyReport <- map["RecivesDailyReport"]
userName <- map["UserName"]
}
}
class BooleanAtttribute: XMLMappable {
var nodeName: String!
var booleanValue: Bool?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
booleanValue <- (map.attributes["xsi:nil"], BooleanTransformeType(trueValue: "true", falseValue: "false"))
}
}
class Firm: XMLMappable {
var nodeName: String!
var extensionData: String?
var firmTypeId: Int?
var id: Int?
var name: String?
var parentFirmId: Int?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
extensionData <- map["ExtensionData"]
firmTypeId <- map["FirmTypeId"]
id <- map["Id"]
name <- map["Name"]
parentFirmId <- map["ParentFirmId"]
}
}
class BooleanTransformeType<T: Equatable>: XMLTransformType {
typealias Object = Bool
typealias XML = T
private var trueValue: T
private var falseValue: T
init(trueValue: T, falseValue: T) {
self.trueValue = trueValue
self.falseValue = falseValue
}
func transformFromXML(_ value: Any?) -> Bool? {
if let value = value as? T {
return value == trueValue
}
return nil
}
func transformToXML(_ value: Bool?) -> T? {
if value == true {
return trueValue
}
return falseValue
}
}
And use the XMLMapper class to map the XML string into the model objects:
let userDTO = XMLMapper<UserDTO>().map(XMLString: xmlString)
Altenatively you can use Requests subspec and the responseXMLObject(completionHandler:) function to map the response directly into the model objects:
Alamofire.request("http://my-web-service-domain.com", method: .get).responseXMLObject { (response: DataResponse<UserDTO>) in
let userDTO = response.result.value
print(userDTO?.id ?? "nil")
}
I hope this is useful.
I had a really unique issue where the server returned an XML that has a JSON as a string. Hope it will help someone.
Basically the XML looked like this:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">{"Response":{"Status":"success","Result_Count":"1","Error_Description":"","Result":{"Login_result":{"user_id":"1","user_type":"1","user_name":"h4cked","user_locked":"False","user_locked_message":""}}}}</string>
As you can see the actual JSON is the {"Response":....
The solution is based only on Alamofire 4.4.
What you need to do is this:
Use the .responsePropertyList
Check for error
Convert the value to Data
Serialize to JSON object
Cast to Dictionary [String : Any]
Here it is:
Alamofire.request(NetworkAPIPaths.pathForLogin(),
method: .get,
parameters: [APIParameters.userName.rawValue : "",
APIParameters.password.rawValue : ""]).responsePropertyList
{ (response : DataResponse<Any>) in
if let error = response.result.error
{
// Error...
}
else if let jsonFullString = response.result.value as? String
{
if let jsonStringAsData = jsonFullString.data(using: .utf8)
{
do
{
let jsonGhost = try JSONSerialization.jsonObject(with: jsonStringAsData, options: [])
if let actualJSON = jsonGhost as? [String : Any]
{
// actualJSON is ready to be parsed :)
}
}
catch
{
print (error.localizedDescription)
}
}
}
tsaiid's answer in Swift 3 and Alamofire 4:
Alamofire.request("http://my-web-service-domain.com", parameters: nil) //Alamofire defaults to GET requests
.response { response in
if let data = response.data {
println(data) // if you want to check XML data in debug window.
var xml = SWXMLHash.parse(data)
println(xml["UserDTO"]["FilmID"].element?.text) // output the FilmID element.
}
}
If you need to use different decoders (JSON, URL, XML) with Alamofire, the best and simplest way I found was using XMLCoder.
Alamofire 5
Swift 5
(It will probably work on older versions)
On the Alamofire's response decodable method, you just need to use XMLDecoder()
#discardableResult
public func responseDecodable<T: Decodable>(of type: T.Type = T.self,
...
decoder: DataDecoder = JSONDecoder(),
...
completionHandler: #escaping (AFDataResponse<T>) -> Void) -> Self {
Something like this:
import XMLCoder
...
dataRequest
.validate()
.responseDecodable(of: T.self, decoder: XMLDecoder()) { (response: DataResponse<T, AFError>) in
And your models just need to conform the Codable protocol.
import Foundation
class ReportModel: Codable {
var connections: ConnectionModel?
var notifications: NotificationsModel?
enum CodingKeys: String, CodingKey {
case connections
case notifications
}
}
I created a GIST with a complete structure of how you can decode XML using Alamofire and I added some examples with nested objects and XML attributes.
https://gist.github.com/Enriquecm/bedf024d8a8f519eb1185fef14adcec3

Resources