Is it possible to have a Alamofire validator that gets the parsed JSON response, check a property and return true / false depending on that value?
I have an API that always returns 200 response codes, but the response has a success property.
I would like to check this property before the responseJSON callback is fired and only call responseJSON if success == true.
Is this possible with custom validators?
Found a solution I feel ok with. First I created extension methods that check for errors and extract the data I'm interested in. I have one success callback and one error callback.
import Foundation
import Alamofire
extension Request {
public func apiSuccess(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: [String:AnyObject] -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: { response in
if let jsonValue = response.result.value as? [String:AnyObject] {
let success = jsonValue["success"] as! Bool
if (success) {
completionHandler(jsonValue["object"] as! [String:AnyObject])
}
}
}
)
}
public func apiError(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: [String] -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: { response in
if let jsonValue = response.result.value as? [String:AnyObject] {
let success = jsonValue["success"] as! Bool
if (!success) {
let errorDict = jsonValue["errors"] as! [String:[String]]
var errors : [String] = []
errorDict.keys.forEach { key in
errors += errorDict[key] as [String]!
}
completionHandler(errors)
}
}
}
)
}
}
Then I can use it like this:
Alamofire.request(.POST, url,
parameters: parameters,
encoding: .JSON)
.apiSuccess { response in
print("Success Callback", response)
}
.apiError { errors in
print("Errors ", errors)
}
I don't think it is. Validator blocks don't receive the response data as arguments, only headers and such.
Related
Hi I just migrated to alamofire 4 and I just want to send the error coming from the server, I found a couple ways but I just want to make sure that this is the correct way, here is my custom responseobject class
public protocol ResponseObject {
init?(response: HTTPURLResponse, representation: Any)
}
enum BackendError: Error {
case network(error: Error)
case dataSerialization(error: Error)
case jsonSerialization(error: Error)
case xmlSerialization(error: Error)
case objectSerialization(reason: String)
}
extension DataRequest {
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
func responseObject<T: ResponseObject>(
queue: DispatchQueue? = nil,
completionHandler: #escaping (DataResponse<T>) -> Void)
-> Self
{
let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
guard error == nil else {
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
debugPrint(result)
return .failure(BackendError.network(error: error!))
}
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
guard case let .success(jsonObject) = result else {
return .failure(BackendError.jsonSerialization(error: result.error!))
}
guard let response = response, let responseObject = T(response: response, representation: jsonObject) else {
return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
}
return .success(responseObject)
}
return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
I add a debugprint so see the error from the server and I see it but do I have to serialize de data again inside the error?? and how can I pass the message to my custom error?
I have a method for GET request in my code:
func makeHTTPGetRequest(path: String, parameters: [String: AnyObject], completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionTask {
let parameterString = parameters.stringFromHttpParameters()
let requestURL = NSURL(string:"\(path)?\(parameterString)")!
let request = NSMutableURLRequest(URL: requestURL)
request.HTTPMethod = "GET"
request.setValue("Bearer " + userInfoDefaults.stringForKey("accessToken")!, forHTTPHeaderField: "Authorization")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler:completionHandler)
task.resume()
return task
}
That is called by an another method that populates a picker view on a specific scene:
func getAffiliateds() -> [String]? {
var affiliateds:[String] = []
makeHTTPGetRequest(baseURL + "affiliateds", parameters: [:], completionHandler: { (data, response, error) in
do {
affiliateds = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! [String]
print (affiliateds)
}
catch { print("Error: \(error)") }
})
return affiliateds
}
I need to get all affiliateds from my webservice and then list it on the picker view. But when I debugged the code I noticed that affiliateds are first returned as a null array and then it is returned with the correct information. I need to return the array from getAffiliateds only when it has already received the data from the webservice. How can I make this?
You can't. Your getAffiliateds() cannot return a value dependent on the asynchronous code that it will run. That is the nature of asynchronous code. Instead, perform a callback of some sort in the completion handler when it is called:
makeHTTPGetRequest(baseURL + "affiliateds", parameters: [:], completionHandler: { (data, response, error) in
do {
affiliateds = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! [String]
print (affiliateds)
// DO SOMETHING HERE
}
}
A frequent strategy is for the caller to provide another completion handler which this completion handler will call.
You have a routine:
func getAffiliateds() -> [String]? {
var affiliateds:[String] = []
makeHTTPGetRequest(baseURL + "affiliateds", parameters: [:], completionHandler: { (data, response, error) in
do {
affiliateds = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! [String]
print (affiliateds)
}
catch { print("Error: \(error)") }
})
return affiliateds
}
And you presumably have some code that does something like:
func populatePicklist() {
let affiliateds = getAffiliateds()
// populate picklist here
}
You should change this to:
func getAffiliatedsWithCompletionHandler(completionHandler: ([String]?) -> ()) {
makeHTTPGetRequest(baseURL + "affiliateds", parameters: [:]) { data, response, error in
do {
let affiliateds = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as? [String] // two notes here: first, define local var here, not up above; second, use `as?` to gracefully handle problems where result was not `[String]`
print (affiliateds)
completionHandler(affiliateds)
}
catch {
print("Error: \(error)")
completionHandler(nil)
}
}
}
func populatePicklist() {
getAffiliatedsWithCompletionHandler { affiliateds in
// populate picklist here
}
// but not here
}
I have a class method, This method will take some time to get data from an http request.
Related part of the method:
do{
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: NSJSONWritingOptions())
} catch {
print("Cound not serilize")
}
let task = session.dataTaskWithRequest(request) {
data, response, error in
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as? NSDictionary
self.result = json! as? [String : AnyObject]
print(self.result)
It will print correct result from JSON, but in other side the object instance authGuestAPI will not:
let authGuestAPI = API(url: apiUrl, params: params, method: "POST")
authGuestAPI.run()
print("RESULT: \(authGuestAPI.result)")
It will print "RESULT: nil"
but If I put NSThread.sleepForTimeInterval(2) before print("RESULT: \(authGuestAPI.result)") It will print correct json data.
you need to create a function when you call the api request. Then implement a completion handler with that function. Add the code in the completion handler that you want to be executed when the value is eventually returned from the api.
I use SwiftyJSON and Alamofire to make download method with completionHandler to fix this issue.
func download(completionHandler: (JSON?, NSError?) -> ()) -> (){
Alamofire.request(.POST, url, parameters: self.params, encoding: ParameterEncoding.URLEncodedInURL , headers: self.header).responseJSON { (result: Response<AnyObject, NSError>) -> Void in
if result.result.isSuccess {
self.hasError = false
self.result = JSON(result.result.value!)
} else if result.result.isFailure {
self.errorCode = result.result.error!.code
self.errorMessage = result.result.error!.localizedDescription
print(result.result.error?.localizedDescription)
self.hasError = true
}
completionHandler(self.result, result.result.error)
}
}
I am calling Url which will give me Json in get() function.
I am calling get() function from another class and try to return result of Json in Array format. but it shows Found null error on return statement . when I tried to print values of Json it writing correctly.
This is my code in swift.
func get() -> NSArray
{
let postEndpoint: String = "Link_For_JSON_Data"
let session = NSURLSession.sharedSession()
let url = NSURL(string: postEndpoint)!
var jsonArray : NSArray?
var jsonArray1 : NSArray?
session.dataTaskWithURL(url, completionHandler: { ( data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
// Make sure we get an OK response
guard let realResponse = response as? NSHTTPURLResponse where
realResponse.statusCode == 200 else
{
print("Not a 200 response")
return
}
// Read the JSON
do
{
if let contentString = NSString(data:data!, encoding: NSUTF8StringEncoding)
{
// Print what we got from the call
jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
print("jsonArray here", jsonArray)
// Update the label
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.getDataFormREST(jsonArray!)
}
}
}
catch
{
print("bad things happened")
}
}).resume()
return jsonArray!
}
func getDataFormREST(resultArray: NSArray) //-> NSArray
{
// let resultDictionary = resultArray[(searchDetails)!-1] as! NSDictionary
testArray = resultArray
print("TESTArray ON ",testArray)
}
You can't write a function that does an async call and then returns the results as the function result. That's not how async code works. Your function queues up the async dataTaskWithURL request, and then returns before it has even had a chance to send the request, much less receive the results.
You have to rewrite your get() function to be a void function (no result returned) but take a completion block. Then, in your data task's completion handler you get the data from the jsonArray and call the get() function's completion block, passing it the jsonArray.
See this project I posted on Github that illustrates what I'm talking about:
SwiftCompletionHandlers on Github
I have written a function for a URL request. This contains a completion handler that returns a dictionary of [String: AnyObject] that is fetched from the URL.
The code for this is:
func getDataAsyncFromURLRequest(url: NSURL, completion: ([String : AnyObject]) -> ()) {
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print("error=\(error)")
return
}
else {
let datastring = NSString(data: data!, encoding: NSUTF8StringEncoding)
if let data = datastring!.dataUsingEncoding(NSUTF8StringEncoding) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String : AnyObject]
completion(json)
} catch {
print("json error: \(error)")
}
}
}
}
task.resume()
}
In some cases, however, I will receive an array of [String : AnyObject] and not the dictionary. So instead of making a duplicate function that takes the array of dictionaries as parameter for the completion handler, I though it was possible to do like this
func getDataAsyncFromURLRequest<T>(url: NSURL, completion: (T) -> ()) {
// code here
}
... and then do like this let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! T, but that gives me this error: Cannot invoke 'getDataAsyncFromURLRequest' with an argument list of type '(NSURL, completion: (_) -> ())'
What would be the best way to make the completion handler accept a parameter with a type decided at runtime, if possible at all?
It's very easy why don't you use AnyObject
func getDataAsyncFromURLRequest(url: NSURL, completion: (AnyObject) -> ()) {
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print("error=\(error)")
return
}
else {
let datastring = NSString(data: data!, encoding: NSUTF8StringEncoding)
if let data = datastring!.dataUsingEncoding(NSUTF8StringEncoding) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
completion(json)
} catch {
print("json error: \(error)")
}
}
}
}
task.resume()
}
And result of JSONObjectWithData can be [AnyObject] (Array) or [String:AnyObject] and tree of those items.
So after got result, you can also check type of result in completion block
Like this
if result is [String:AnyObject]
...
else if result is [AnyObject]
...
else
//throw error : because then it is not JSON