RX Alamofire Swift empty response JSON - swift

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

Related

Swift Combine: handle no data before decode without an error

My API usually returns a certain format in JSON (simplified notation):
{
status: // http status
error?: // error handle
data?: // the response data
...
}
In my Combine operators, I take the data from a URLSession dataTaskPublisher and parse the response into a Decodable object that reflects the above schema. That works great.
However, I have an endpoint that returns the HTTP status code 201 (operation successful), and has no data at all. How would I chain this with my operators without throwing an error?
This is what I have:
publisher
.map { (data, response) in
guard data.count > 0 else {
let status = (response as! HTTPURLResponse).statusCode
return Data("{\"status\": \(status), \"data\": \"\"}".utf8)
}
return data
}
.mapError { CustomError.network(description: "\($0)")}
.decode(type: MyResponse<R>.self, decoder: self.agent.decoder)
.mapError { err -> CustomError in CustomError.decoding(description: "\(err)") }
...
As you can see, I simply construct an appropriate response, where the response's "data" is an empty string. However, this is ugly and a bit hacky, and I do not see the reason, why the pipeline should continue with parsing, etc, when I already have all I need. How can I interrupt it and finish the pipeline successfully for its final subscriber?
I would suggest creating a separate Publisher for handling the specific endpoint which doesn't return any Data. You can use a tryMap to check the HTTP status code and throw an error in case it's not in the accepted range. If you don't care about the result, only that there was a successful response, you can map to a Void. If you care about the result (or the status code), you can map to that too.
extension URLSession.DataTaskPublisher {
func emptyBodyResponsePublisher() -> AnyPublisher<Void, CustomError> {
tryMap { _, response in
guard let httpResponse = response as? HTTPURLResponse else { throw CustomError.nonHTTPResponse }
let statusCode = httpResponse.statusCode
guard (200..<300).contains(statusCode) else { throw CustomError.incorrectStatusCode(statusCode) }
return Void()
}.mapError { CustomError.network($0) }
.eraseToAnyPublisher()
}
}

Alamofire and responseJSONDecodable with either Dictionary or Array type

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
}

How to return a modified result in a promise as a promise

I'm using promiseKit 6 with the extensions for Alamofire. In the function bellow I'm looking to return Promise<JSON> (I use swiftyJson) but the response from the alamofire call is a Promise containing a tuple: Promise<(json: Any, response: PMKAlamofireDataResponse)>
when I get that from the first then, how can I continue to return just the json part? thanks :)
return firstly {
self.requestClient.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON()
}.then { arg in
let (json, rsp) = arg
return json
}
I also get this error: Cannot convert return expression of type 'Promise<_.T>' to return type 'Promise<JSON>'
on the line: }.then { arg in ...
You should cast Any to JSON, try this (not tested), but documentation said you could use map/compactMap https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md
return firstly {
self.requestClient.request(url, method: .get, parameters: nil,
encoding: JSONEncoding.default, headers: nil).responseJSON()
}.compactMap { data, rsp in
return data as? JSON
}

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)

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!