Alamofire completion handler issue - ios

I have a POST request with certain data that I would like to receive. However when I use responseArray I would be thrown this error
json data is nil
but when I use responseJSON everything would be fine. Why is this so?
This code does not work:
Alamofire.request(.POST, Data.todoEndpoint, parameters: parameters)
.responseArray { (response: Response<[Particulars], NSError>) in
print(response.request)
print(response.response)
print(response.result)
if let result = response.result.value
{
do{
print(Realm.Configuration.defaultConfiguration.fileURL)
let realm = try Realm()
realm.add(result, update: true)
}
catch let err as NSError {
print("Error with realm: " + err.localizedDescription)
}
}
else
{
print("JSON data is nil.")
}
}
But this is fine:
Alamofire.request(.POST, Data.todoEndpoint, parameters: parameters)
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let result = response.result.value
{
print(result)
}
else
{
print("JSON data is nil.")
}
}
I need responseArray so that I can have (response: Response<[Particulars], NSError>) and store my JSON response into realm
UPDATE
This is the Particulars class that I want to connect to. I'm trying to map my JSON objects to Realm based on this article https://blog.hyphe.me/realm-and-alamofire-in-a-effective-harmony/
import Foundation
import RealmSwift
import ObjectMapper
class Particulars: Object, Mappable {
dynamic var name = ""
dynamic var email = ""
dynamic var id = ""
dynamic var profilePicture = ""
dynamic var username = ""
dynamic var apiToken = ""
override static func primaryKey() -> String? {
return "id"
}
//Impl. of Mappable protocol
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
email <- map["email"]
profilePicture <- map["profile_picture"]
username <- map["username"]
apiToken <- map["api_token"]
}
}
And this is the JSON response:
[
"name" : "Jonny Walker",
"api_token" : "qwertyuiop1234567890",
"profile_picture" : "http:default_profile_picture.jpg",
"id" : 10,
"email" : "jwalker#gmail.com",
"username" : "jonny"
]
UPDATE 2
My completion handler is working fine with responseObject but my realm.add xxx is throwing up this error
Cannot convert value of type 'String' to expected argument type 'Object'
My code can be found here https://codeshare.io/v4M9M (lines 19- 25)

The Alamofire page shows how to handle response and does not list the responseArray method. https://github.com/Alamofire/Alamofire#response-serialization
You can use the responseJSON to get the JSON and convert into an array that you want. It would look something like this, (make changes to this based on your JSON response)
Alamofire.request(.POST, Data.todoEndpoint, parameters: parameters)
.responseJSON { response in
guard response.result.isSuccess else
{
//handle error
return
}
guard let value = response.result.value as? [String: AnyObject],
particularsArrayJson = value["particulars"] as? [[String: AnyObject]]
else{
//Malformed JSON, handle this case
}
var particulars = [Particulars]()
for particularsDict in paricularsArrayJson{
particulars.append(Pariculars(json:particularsDict))
}
}
You have to have an initializer in your Particulars that will initialise from the JSON provided.
Update:
The realm add method takes an instance of a class which extends from Object
Object is a class provided by Realm. So you may have to read up the documentation more.
You should be doing this instead
realm.add(particulars, updated:true)

Related

Mapping allHeaderFields in AlamofireObjectMapper response

I am using AlamofireObjectMapper
and response from my server is in JSON but it also include some key in the headers
Alamofire.request(URL).responseObject { (response: DataResponse<MyMappable>) in
let weatherResponse = response.result.value
print(weatherResponse?.location)
}
and my map able class is like this
class MyMappable: Mappable {
var location: String?
required init?(map: Map){
}
func mapping(map: Map) {
location <- map["location"]
}
}
all of the above code is working but my problem is I want to map some value is header, I can get these header in the response only
let allHeaders = response.response?.allHeaderFields
if let headers = allHeaders as? [String: String], let someValue = headers["key"]
{
print("my value is: \(someValue)")
}
but I want to map it in the MyMappable class, is it possible ?
I could not find a way to map header Fields with payload
what I could find was, we can get all header fields from response and store it in our class in two ways.
first one is like this
Alamofire.request(URL).responseObject { (response: DataResponse<MyMappable>) in
let myMappable = response.result.value
myMappable.allHeaderFields = response.response?.allHeaderFields
print(myMappable?.location)
}
and the second way (which one used by me) if you are using generic class as parent then you can cast your response like blow
Alamofire.request(URL).responseObject { (response: DataResponse<MyMappable>) in
let myMappable = response.result.value
if let genericResp = myMappable as? GenericResponse<AnyGeneric> {
genericResp.mHeaders = response.response?.allHeaderFields
}
}

Failure response from alamofire swift3

Hello I get stucked with alamofire.
let keys = [CNContactPhoneNumbersKey, CNContactGivenNameKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
let contactStore = CNContactStore()
do {
try contactStore.enumerateContacts(with: request) {
(contact, stop) in
// Array containing all unified contacts from everywhere
self.contacts.append(contact)
}
}
catch {
print("unable to fetch contacts")
}
var contactArray = [[String:String]]()
for i in 0..<contacts.count{
var mobiles = ""
for num in contacts[i].phoneNumbers {
mobiles = num.value.stringValue
}
var theDict = ["contact_id": "\(i)", "full_name": contacts[i].givenName, "mobile_number": "\(mobiles)"]
contactArray.append(theDict)
}
dictParams["contacts"] = dictContacts
let theParams = ["contacts":contactArray] as [String:AnyObject]
print("dict theParams: \(theParams)")
this is the printed parameters
dict theParams: ["contacts": <Swift._SwiftDeferredNSArray 0x604000233280>(
{
"contact_id" = 0;
"full_name" = Kate;
"mobile_number" = "62123123";
},
{
"contact_id" = 1;
"full_name" = Daniel;
"mobile_number" = "621231123";
},
{
"contact_id" = 2;
"full_name" = John;
"mobile_number" = "625435345";
}
)
]
and i send it to alamofire with this code
func sendFriendList(dictContacts: [String:AnyObject] ,successBlock:((_ isSuccess: Bool, _ theFriendsResponse: FriendsResponse)->Void)?,errorBlock:((_ errorString:String)->Void)? )-> Void
{
Alamofire.request("url/ListRetrieve",
method: .post,
parameters: dictContacts,
encoding: URLEncoding.default,
headers: headers)
.responseJSON { response in
print("this is response retrieve \(response)")
and the response from alamofire:
this is the response = retrieve FAILURE: responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 1." UserInfo={NSDebugDescription=Invalid value around character 1.}))
but if I change the parameters only string like this
["contacts":"test contact"]
the alamofire response become success and it's work.
please help me :) thank you
It's not the Alamofire issue. The api needs the "contacts" parameter as String and you are sending an Array of Dictionaries. If you are sending the Array data in "contacts" parameter ask the api developers to change the parameter type or if possible send string as data in "contacts" parameter.
The issue :
this is the response = retrieve FAILURE: responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 1." UserInfo={NSDebugDescription=Invalid value around character 1.}))
is also stating the invalid character only.
My Parameters is like
// .toJson to your array object or dictonary object and try out
let parameters: [String : Any] = [
"products":[
["pid":"72","qnty":"1"],
["pid":"4","qnty":"1"],
["pid":"3","qnty":"1"]
].toJson ?? "[]",
]
Use this extension for converting Array object to JSON string.
extension Array where Element: Codable {
public var toData: Data {
let encoder = JSONEncoder()
do {
return try encoder.encode(self)
}
catch {
fatalError(error.localizedDescription)
}
}
public var toJson: String? {
return toData.toJson
}
}
Use responseString instead of responseJSON. Then use JSONSerialization to convert response to JSON.
Something like :
JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}

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

Occuring issue while passing data between classes

I'm trying to pass data from my Data class to my Login class (both files are separate.), but it throws 2 errors from "Login" class as it appears unable to receive the data
Below are the errors shown in Login.swift :
Error 1
Instance member 'dataObj' cannot be used on type 'Login'
Error 2
Expected declaration
Login.swift code
import Foundation
import Alamofire
import SwiftyJSON
class Login {let dataObj = Data(userName: "username", passWord: "password")
let endPoint = dataObj.todoEndPoint
let parameters = [
"username": dataObj.userName,
"password": dataObj.passWord
]
Alamofire.request(.POST, endPoint, parameters:parameters)
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let JSON = response.result.value {
print("Did receive JSON data: \(JSON)")
}
else {
print("JSON data is nil.")
}
}
}
Data.swift code
import Foundation
import Alamofire
import SwiftyJSON
class Data {
var userName:String!
var passWord:String!
let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
init(userName : String, passWord : String) {
self.userName = userName
self.passWord = passWord
}
}
Screenshot of errors
You just have to change the Data instance property to class property by using static
class Data {
static var userName:String!
static var passWord:String!
static let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
}
and set direct values in userName and passWord.
in from login set value
Data.userName = "yourusername"
Data.passWord = "password"
2.
or you can also create Data object
class Data {
var userName:String!
var passWord:String!
let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
init(userName : String, passWord : String) {
self.userName = userName
self.passWord = passWord
}
and in login class
class Login {
static let dataObj = Data(userName: "usename", passWord: "password")
lazy var endPoint = Login.dataObj.todoEndpoint
let parameters = [
"username": dataObj.userName,
"password": dataObj.passWord
]
func getRequest() {
Alamofire.request(.POST, endPoint, parameters:parameters)
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let JSON = response.result.value {
print("Did receive JSON data: \(JSON)")
}
else {
print("JSON data is nil.")
}
}
}
}
UPDATED
I guess you should create a singleton object to get a values of users everywhere.
class Data {
static let sharedInstance = Data()
static let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
var userName: String?
var passWord: String?
// for prevent from creating this class object
private init() { }
}
And in Login class.
class Login {
init(userName: String, passWord: String) {
Data.sharedInstance.userName = userName
Data.sharedInstance.passWord = passWord
}
/// call this method to login
func getRequest() {
print(Data.sharedInstance.userName)
print(Data.sharedInstance.passWord)
Alamofire.request(.POST, Data.endPoint, parameters: ["userName": Data.sharedInstance.userName, "passWord": Data.sharedInstance.passWord])
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let JSON = response.result.value {
print("Did receive JSON data: \(JSON)")
}
else {
print("JSON data is nil.")
}
}
}
}
And from some other class create a Login instance and pass userName and Password values from there and call getRequest method using Login object.
According to your Data class implementation you should use it like :
class Login {
let dataObj = Data(userName : "username", passWord : "password")
let endPoint = dataObj.todoEndPoint
let parameters = [
"username": dataObj.userName,
"password": dataObj.passWord
]
Alamofire.request(.POST, endPoint, parameters:parameters)
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let JSON = response.result.value {
print("Did receive JSON data: \(JSON)")
}
else {
print("JSON data is nil.")
}
}
}
If you want to get static data from Data then use singleton/shared instance.

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