I'm using Alamofire library for connecting with an API in iOs. I have a problem in one of the connection, and I don't know if it is because of the data encoded in the body or any other thing. In order to detect my error, I'm trying to print in the console the request body for checking if I'm sending the correct data structure.
My code is the following:
func updateUser (#user: User, completionHandler: (responseObject: User?, error: AnyObject?) -> ()) {
let parameters = [
"_id": "\(user._id!)",
"email": "\(user.email!)",
"media": "\(Mapper().toJSONArray(user.media!))",
"blogs": "\(Mapper().toJSONArray(user.blogs!))"
]
var manager = Alamofire.Manager.sharedInstance
manager.request(.PUT, apiUrl + "/route/to/api", parameters: parameters, encoding: .JSON)
.responseObject{ (req: NSURLRequest, res: NSHTTPURLResponse?, user: User?, data: AnyObject?, error: NSError?) in
if(error != nil) {
NSLog("Error API updateUser: \(error)")
}
else {
completionHandler(responseObject: user as User?, error: data)
}
}
}
User is a Mappable object, since I'm using ObjectMapper combined with Alamofire. User is defined by the following code:
class User: Mappable {
var _id: String?
var name: String?
var media: [Media]?
init(_id: String, name: String, media: [Media]){
self._id = _id;
self.name = name;
self.media = media
}
required init=(_ map: Map){
mapping(map)
}
func mapping(map: Map){
_id <- map["_id"]
name <- map["name"]
media <- map["media"]
}
}
Media is defined like User, but with different variables.
Also, I would like to know, in addition of printing request body, if I could include the parameters to Alimofire request in a more efficient way (something like parsing the User object and substituting it for the parameters variable)
Any idea about my problems?
EDIT:
Following the suggestion of #Travis, finally I found the solution for printing the request body. Below you could find the code:
println("request body: \(NSString(data:req.HTTPBody!, encoding:NSUTF8StringEncoding) as String?)")
About passing as parameters an object I couldn't work it, I followed the official documentation, but I could do it.
For Swift 3+
print(NSString(data: (response.request?.httpBody)!, encoding: String.Encoding.utf8.rawValue))
The answer to your first question is,
println("request body: \(request.HTTPBody)")
As far as your 2nd question goes, there's a whole section on API Parameter Abstraction as well as CRUD & Authorization on the Alamofire main page.
Added the below extension for the Request class for printing the logs.
extension Request {
public func debugLog() -> Self {
#if DEBUG
debugPrint("=======================================")
debugPrint(self)
debugPrint("=======================================")
#endif
return self
}
}
To use the extension, just use debugLog() after defining your request, like so:
Alamofire.request(url).debugLog()
.responseJSON( completionHandler: { response in
})
reference url : link
Swift 5
print(response.debugDescription)
For Swift 4 & Swift 5, just like that :
String(data: data!, encoding: String.Encoding.utf8)
If not in DefaultDataResponse extension or object, replace data with yourObject.response.data
From Alamofire documentation here https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#curl-command-output
You can get curl request description
AF.request("https://httpbin.org/get")
.cURLDescription { description in
print(description)
}
.responseDecodable(of: DecodableType.self) { response in
debugPrint(response.metrics)
}
Just to turn it a bit easier.
if let requestBody = request.request?.HTTPBody {
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(requestBody, options: [])
print("Array: \(jsonArray)")
}
catch {
print("Error: \(error)")
}
}
Related
First of all, I'm totally new to Swift and iOS development, but decided to try just for some fun. I'm also taking a Nanodegree from Udacity on the same at the moment. I got the base screen designed, and am now trying to design a easy to use API to define backend calls (not required in class, but I picked it up out of curiosity).
struct ApiDefinition<RequestType: Codable, ResponseType: Codable> {
let url: String
let method: HttpMethod
let headers: [String : String]?
func call(withPayload payload: RequestType, completion: #escaping (ResponseType?, Error?) -> Void) throws {
let url = URL(string: self.url)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 500)
request.httpMethod = method.rawValue
request.httpBody = try JSONEncoder().encode(payload)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
completion(nil, error)
return
}
let decoder = JSONDecoder()
let value = try? decoder.decode(ResponseType.self, from: data)
completion(value, nil)
}
task.resume()
}
}
enum HttpMethod: String {
case get = "GET"
case post = "POST"
}
This is working as expected, and I define the endpoints as follows.
let udacitySessionApi = ApiDefinition<SessionRequest, SessionResponse>(
url: baseUrl + "/v1/session",
method: .post,
headers: [
"Content-Type": "application/json",
"Accept": "application/json"
]
)
Which in turn lets me to call the API whenever I want to with the following code.
do {
try udacitySessionApi.call(withPayload: request) { response, error in
guard let response = response else {
print(error!.localizedDescription)
return
}
print(response)
}
} catch {
print(error.localizedDescription)
}
So far, so good. Now what I wanted to do is allow the Codable to wrap itself for conciseness. However, the problem now is that some request types often needs to be nested in the encoded output, and I didn't want to write extra structs.
So I thought if the RequestType is a Codable and also conforms to a Wrappable protocol (I wrote), I can automate that.
So I wrote the protocol Wrappable as follows:
protocol Wrappable: Codable {
func wrap() -> Codable
}
And then in my request type I made it conform to that protocol.
struct SessionRequest: Codable, Wrappable {
// ...
func wrap() -> Codable {
return ["wrapped-key": self]
}
}
Now to access this thing, I wrote a function
private func getEncodable<T: Codable>(_ payload: T) -> Codable {
if let payload = payload as? Wrappable {
return payload.wrap()
}
return payload
}
But problem now came with the JSONEncoder that the signature of encode is
open func encode<T>(_ value: T) throws -> Data where T : Encodable
and the error message that I received is this:
Value of protocol type 'Codable' (aka 'Decodable & Encodable') cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols
happening on the line
request.httpBody = try JSONEncoder().encode(getEncodable(payload))
// ^^
As far as I understand, if a struct confirms to Codable protocol, then it automatically confirms to the Encodable protocol too isn't it?
So, I thought to explicitly change Codable return type in my wrap function, the Wrappable protocol and also the getEncodable function to Encodable, and my error message changed to this:
Value of protocol type 'Encodable' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols
Great, now I'm even more confused. What does value of protocol type mean?
private func makeRequest<T where T:MappableNetwork>(method method: Alamofire.Method, url: String,
parameters: [String: AnyObject]?, keyPath: String, handler: NetworkHandler<T>.handlerArray) -> Request {
let headers = [
"Authorization": "",
]
return Alamofire
.request(method, url, parameters: parameters, encoding: .URL, headers: headers)
.validate()
.responseArray(keyPath: keyPath) { (response: Alamofire.Response<[T], NSError>) in
if let error = response.result.error {
if let data = response.data {
let error = self.getError(data)
if error != nil {
handler(.Error(error: error!))
return
}
}
handler(.Error(error: error))
} else if let objects = response.result.value {
handler(.Success(data: objects))
}
}
}
I converted code swift 2.x to 3.x and I getting error Type expression is ambiguous without more context.
The error you mentioned tells you that the compiler cannot determine the exact type of the value you entered.
You started with a period, something has to be before the period. Sometimes the compiler can understand without your help. That's not this case, it has several options so it's ambiguous and it asks you to tell exactly the class name that you meant.
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)
}
I am working on swift 3 application and want to build login system using REST API. First I wanted a way to post data to server (PHP + MYSQL) with parameters so I found this post.
HTTP Request in Swift with POST method
Now I wanted place this code in a method as helper so I can utilise this method from anywhere in app. Hence followed this way:
Where to put reusable functions in IOS Swift?
Current code is as follow:
import Foundation
class Helper {
static func postData(resource: String, params: [String: String]) -> [String:String] {
var request = URLRequest(url: URL(string: "http://localsite.dev/api/\(resource)")!)
request.httpMethod = "POST"
var qryString: String = "?key=abcd"
for (paramKey, paramVal) in params {
qryString = qryString.appending("&\(paramKey)=\(paramVal)")
}
request.httpBody = qryString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("Error")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("Error on HTTP")
return
}
let responseString = String(data: data, encoding: .utf8)
print("success and here is returned data \(responseString)")
}
task.resume()
return ["data" : "some data"]
}
}
Call this using
let loginDetails: [String: String] = ["email": emailTxt.text!, "pass": passTxt.text!]
Helper.postData(resource: "login", params: loginDetails)
In above method rather then printing data I want to return data as per below 4 conditions.
1.If error in request data then I want to return as
[“status”: false, “message”: “Something wrong with request”]
2.If error in HTTP request
[“status”: false, “message”: “Resource not found”]
3.If login fail
[“status”: false, “message”: “Wrong login details”]
4.If login success
[“status”: true, “message”: “Login success”]
If you want to use a third party library for handling HTTP request, I strongly recommend Alamofire.
When I wanna handle HTTP requests I usually create a singleton class:
class HttpRequestHelper {
static let shared = HttpRequestHelper()
func post(to url: URL, params: [String: String], headers: [String: String], completion: (Bool, String) -> ()){
//Make the http request
//if u got a successful response
// parse it to JSON and return it via completion handle
completion(true, message)
//if response is not successful
completion(false, message)
}
}
And you can use it everywhere:
class AnotherClass: UIViewController {
HttpRequestHelper.shared.post(to: url, params: [:], header: [:],
completion:{
success, message in
print(success)
print(message)
})
}
To the POST method more reusable and not just specific to an endpoint, I usually make the completion handler params as Bool, JSON. And then I handle the JSON response from wherever I call the method.
Oh and I use SwiftyJson to parse json, it's the simplest.
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