I am trying to use JSONDecoder
to decode a json response from my server using Alamofire. When I decode the response with a guard
, it works without any issues. The side-effect of this approach is that I can't tell what the issue is when the decode actually fails.
guard let result: TResponseData = try? decoder.decode(TResponseData.self, from: response.data!) else {
self.logger.error("Unable to decode the response data into a model representation.")
return
}
So instead I'm wanting to use a do { } catch { }
but I can't figure out how exactly I'm supposed to use it within the Alamofire responseJSON
callback.
This is what I've currently got:
Alamofire.request(completeUrl, method: .post, parameters: parameters, encoding: encoding, headers: headers)
.validate()
.responseJSON { (response) -> Void in
self.logger.info("POST Response: \(String(describing:response.response?.statusCode))")
switch response.result {
case .success(_):
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Date.toTMDBDate)
do {
let _ = try decoder.decode(TResponseData.self, from: response.data!)
} catch DecodingError.dataCorrupted(let error) {
self.logger.error(error.underlyingError)
return
}
completion(result)
return
case .failure(let error):
//....
}
What I am given with this code however is a compiler error on the .responseJSON { (response) -> Void in
line.
Invalid conversion from throwing function of type '(_) -> Void' to non-throwing function type '(DataResponse) -> Void'.
The guard code works fine, and if I change the try
to a try?
or force an unwrap, it compiles - I just don't get to have my catch handle the actual error.
If I change the catch
block so that it does not include any pattern, then the code compiles.
catch {
return
}
This doesn't give me anything over what my guard
was giving me. I really want to capture the error encountered with the decode
operation. Am I using the wrong pattern? Why does using the DecodingError.dataCorrupted
pattern seemingly change the callback signature?
JSONDecoder
can throw errors other than DecodingError.dataCorrupted
; you need to be able to handle the case of an arbitrary Error
being thrown. So, if you want to handle that error, you'll want an unconditional catch {}
block.
You can also:
responseData
instead of responseJSON
as you're doing your own deserialisation with JSONDecoder
.unwrap()
method on Alamofire's Result
type in order to coalesce the network error with the decoding error, if desired.This is what that looks like:
Alamofire
.request(
completeUrl, method: .post, parameters: parameters,
encoding: encoding, headers: headers
)
.validate()
.responseData { response in
self.logger.info(
"POST Response: \(response.response?.statusCode as Any)"
)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Date.toTMDBDate)
do {
let result = try decoder.decode(
TResponseData.self, from: response.result.unwrap()
)
completion(result)
} catch {
self.logger.error(error)
}
}
Although one thing to note here is that you're not calling completion
if the request fails; I would personally change that such that you do, and propagate the error back by having the completion
take a Result<TResponseData>
parameter.
In that case, you can use Result
's flatMap(_:)
method rather than unwrap()
and a catch {}
block:
func doRequest(_ completion: @escaping (Result<TResponseData>) -> Void) {
let completeURL = // ...
let parameters = // ...
let encoding = // ...
let headers = // ...
Alamofire
.request(
completeURL, method: .post, parameters: parameters,
encoding: encoding, headers: headers
)
.validate()
.responseData { response in
self.logger.info(
"POST Response: \(response.response?.statusCode as Any)"
)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Date.toTMDBDate)
// if response.result is successful data, try to decode.
// if decoding fails, result is that error.
// if response.result is failure, result is that error.
let result = response.result.flatMap {
try decoder.decode(TResponseData.self, from: $0)
}
.ifFailure {
self.logger.error($0)
}
completion(result)
}
}