Alamofire and responseJSONDecodable with either Dictionary or Array type - swift

I am working with Swift and Alamofire to access an API. I am using code similar to the following:
Almofire.request(url!, method: HTTPMethod.get, parameters: nil, encoding: JSONEncoding.default, headers: headers).validate() .responseJSONDecodable { (response: DataResponse<[GeoEntity]>) in
response.result.ifFailure({
let responseError = try? AUAPIError(response)
guard (responseError?.type == nil) else{
//if we get .InvalidGrant, get new token by logging in the normal way.
if (responseError?.type == .UnauthorizedClient){
AUService.shared.delegate?.unAuthorizedAccess(error: responseError!)
completion(nil, responseError)
}
return
}
})
response.result.ifSuccess({
completion(response.result.value, nil)
})
}
```
The problem I am facing is that DataResponse<[GeoEntity]> can either return an array of GeoEntity objects, or a single one as a dictionary. Using Alamofire 5 and responseJSONDecodable, how do I cater to both kinds that are returned. Or do I have to resort to the older way of decoding?

Please Check for type like
if response is NSArray{
//if type is array
}
if response is NSDictionary
{ //if type is Dictionary
}

Related

Swift 4 - How can I call a piece of code which returns out of the function in multiple functions without duplicating code?

I am working with a poorly designed API (I don't have control over it) where even if the access token is expired, it still returns a HTTP success code but includes the 401 Unauthorized in the actual response body. So simply checking the HTTP status code isn't sufficient and I need to check the actual response.
I am making many network requests in my app to this API and when I receive the response, I need to first check whether the response is an array or a dictionary. If array, then we are good. If it's a dictionary, then I need to check the "error" field in the response dictionary which will have the 401 Unauthorized.
So every time I receive the JSON response, I have the following piece of code to return out of the function if it's an error dictionary:
guard !(myJSON is NSDictionary) && (myJSON as! NSDictionary).value(forKey: "error") != nil else {
print("Error: ", MyAppError.accessTokenExpired)
return
}
I am wondering if there is a way to prevent duplicating this piece of code in every network request function I have? Can I have this somewhere and simply call it without duplicating these lines of code each time?
Wrap it in a function like this one
func isErrorResponse(_ response: Any) -> Bool {
if let dict = response as? [String: Any], let error = dict["error"] {
print("Error: \(MyAppError.accessTokenExpired)")
return true
}
return false
}
You should use the swift data types if you are using Swift language. Although if myJSON validates with Dictionary then it will definitely be validated with [String: Any].
Create function like:
func isValidResponse(_ json: Any) -> Bool {
guard let jsonDict = json as? [String: Any], let let error = dict["error"] else { return true }
print("Error: \(error.localizedDescription)")
return false
}
Use it as:
guard YourClass.isValidResponse(myJSON) else { return }
// Valid Response - Code here...

Can't make post request using params as dictionary with Swift 4 & Alamofire

I'm trying to learn to call API with/without library. But the problem here confuses me.
I have params like this:
let parameters: [String:String] =
["key":"MY_KEY" ,
"q":sourceText,
"source": sourceLanguage),
"target": target)]
let headers: HTTPHeaders = [ "Content-type": "application/json"]
I make a post call like this:
Alamofire.request(urlString, method: HTTPMethod.post, parameters: parameters, headers: headers)
.responseJSON{ response in
guard response.result.error == nil else {
print("error calling POST on /todos/1")
print(response.result.error!)
return
}
// make sure we got some JSON since that's what we expect
guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
print("Error: \(response.result.error)")
return
}
By this I get an error 403, saying that I do not have a valid API key (I tested the key with postman, and it is okay).
After many efforts, I have to change the code like this
let stringparams = "key=MY_KEY&q=\(sourceText)&source=\(sourceLanguage)&target=\(target)"
request.httpBody = stringparams.data(using: String.Encoding.utf8)
and using this: Alamofire.request(request)
it works!
I'm using Google Cloud Translation api. And the web use a REST api as said here: https://cloud.google.com/translate/docs/reference/translate
So why can't I use params as dictionary, but using the string (like formdata) will work here?
My guess is Alamofire didn't make the right BODY for the request from the parameters because other arguments is okay. But I don't know why.
And I think Google should accept a json params as they mentioned, in stead of using form data? I did try the original method, but it didn't work with JSON.
From what actually works for you it looks like you need to encode the parameters in the same style as a query. Try this:
struct ParameterQueryEncoding: ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try urlRequest.asURLRequest()
request.httpBody = parameters?
.map { "\($0)=\($1)" }
.joined(separator: "&")
.data(using: .utf8)
return request
}
}
You should then be able to perform the original call that you had before:
Alamofire.request(urlString,
method: HTTPMethod.post,
parameters: parameters,
encoding: ParameterQueryEncoding(),
headers: headers)
.responseJSON { response in
...
}
Try by using JSON encoding. Make sure you have removed ) from dictionary.
Alamofire.request(URL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)

Error 401: Alamofire/Swift 3

This code previously works, and suddenly after several works around it stopped and return an Error 401.
Alamofire.request(WebServices.URLS().TabDataURL, method: .post, parameters: reqParams).validate().responseJSON { response in
let statusCode = (response.response?.statusCode) //Get HTTP status code
guard response.result.isSuccess, let value = response.result.value else {
// FIXME:you need to handle errors.
print("Status, Fetching News List:", statusCode)
return
}
I have check via Postman, the parameters are correct. Infact, I can also login (by passing 2 parameters). But when I want to pull in a JSON data from server, I am getting 401.
my main project doesn't have an error. but instead of returning the JSON data, it gave me an Error 401. my other projects (same code format, same server & parameters) is giving me this error.
Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.} it worked!
Also, I have check with my other projects -- it seemed I cannot connect aswell. So I suspect it could be because of Alamofire, or my Xcode?
Anyone can help me?
Hi i think your server Response is not correct because, as error indicate object should not start with array same issue i come across tell backend developer to send response in dictionary,
{NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
let headers = [String: String]()// To add header if any
Alamofire.request(path,method: mType, parameters: parameters, encoding: JSONEncoding.default, headers : headers) .responseJSON
{ response in
//----- this code always works for me. & You don't need add header if not required.
if let JSON = response.result.value {
print("JSON: \(JSON)")
if response.response?.statusCode == 200
{
successCompletionHandler(JSON as! NSDictionary)
}
else if response.response?.statusCode == 401
{
failureCompletionHandler(JSON as! NSDictionary)
}
else
{
failureCompletionHandler(JSON as! NSDictionary)
}
}
else
{
print("error message")
failureCompletionHandler([WSAPIConst.ERROR:WSAPIConst.ERROR_MESSAGE])
}
}

RX Alamofire Swift empty response JSON

I am using Alamofire with rx and i'm having one issue that if the call is a success (status 200) there is no JSON returned. This triggers my error code. If I get status 400 I get JSON so the call is fine.
How do I specify that the response is JSON, but not to error if empty and status code 200?
Thanks!
func createUser(httpBody: AccountDetails!) -> Observable<(NSHTTPURLResponse, AnyObject)> {
return Alamofire.Manager.rx_request(.POST,
APIService.REGISTER_ENDPOINT,
parameters: httpBody.getParameters(),
encoding: .JSON,
headers: nil)
.flatMap {
$0
.validate(statusCode: 200..<501)
.validate(contentType: ["application/json"])
.rx_responseJSON()
.map { (response:NSHTTPURLResponse, object:AnyObject) -> (NSHTTPURLResponse, AnyObject) in
return (response, object)
}
}
}
EDIT:
I fixed this by changing .rx_responseJSON() to .rx_responseData() and returning NSData instead of AnyObject as the second parameter. Now the call succeeds and I manually convert the data to JSON. Not sure if this is the correct solution but it will suffice for now. Happy to hear better solutions though.
Thanks
I fixed this by changing .rx_responseJSON() to .rx_responseData() and returning NSData instead of AnyObject as the second parameter. Now the call succeeds and I manually convert the data to JSON. Not sure if this is the correct solution but it will suffice for now. Happy to hear better solutions though. Thanks
func createUser(httpBody: AccountDetails!) -> Observable<(NSHTTPURLResponse, NSData)> {
return APIManager.sharedManager.rx_request(.POST,
APIService.REGISTER_ENDPOINT,
parameters: httpBody.getParameters(),
encoding: .JSON,
headers: nil)
.flatMap {
$0
.validate(statusCode: [200,400,500])
.validate(contentType: ["application/json"])
.rx_responseData()
.map { (response:NSHTTPURLResponse, object:NSData) -> (NSHTTPURLResponse, NSData) in
return (response, object)
}
}
}

returning JSON as string in Swift using Swifty

I'm using Alamofire and Swifty and am able to make my API POST and get data back successfully. However, I'm unsure of how to get the data that I'm printing and be able to return it as a string.
In the below, the println's print fine. However, when I use the same json["ticket"] as the return, I get 'JSON' is not convertible to 'Void'
let encoding = Alamofire.ParameterEncoding.URL
// Fetch Request
Alamofire.request(.POST, "http://api.co/?v=1", parameters: bodyParameters, encoding: encoding)
.validate(statusCode: 200..<300)
.responseJSON{(request, response, data, error) in
if (error == nil)
{
var json = JSON(data!)
println(json["ticket"])
return json["TOKEN"]
}
else
{
println("HTTP HTTP Request failed: \(error)")
}
The problem is you are returning "Dictionary" from the closure, while Closure return type is Void. So, you need to get that in a completion handler.
For better idea, you can take a look at this solution. Hope it helps!