I have an API client that uses generic API response that conforms to Codable Protocol and uses JSONDecoder to decode the response as shown below, how do I handle having a response which doesn't return JSON ( status code 201 created)?
dataRequest.validate().responseJSON { response in
if let error = response.error {
completion(.failure(error.localizedDescription))
} else if let data = response.data {
do {
let apiResponse = try JSONDecoder().decode(T.Response.self, from: data)
completion(.success(apiResponse))
} catch {
completion(.failure(error.localizedDescription))
}
} else {
completion(.failure("Something went wrong, please try again later."))
}
}
It returns this error:
the response could not be serialized input data was nil or zero-length
In this case you can look at the statusCode property of the response (assuming that it is a HTTPURLResponse) and make your determination about whether or not there will be a body to parse. I would put it immediately after the error check.
Related
This question already has answers here:
Alamofire Response Serialization Failed
(2 answers)
Closed 6 months ago.
I have an API where I PUT stuff to. I need to make sure to wait until I get an http 200 response from the server, but I don't know how to await that using Alamofire because my response itself if empty. So it's just an http 200 with no content.
I only can find async functions that e.g. serialize a String or Data or a Decodable, but they don't work if my response is empty.
Is there a way to await something like that in Alamofire?
I know that your question is about async/await from Alamofire, but is good to know that the http status codes 204 and 205 are exactly for this. Which means that if you have access to the server code you could send the empty responses with the http status code 204 and 205 instead of 200 and then Alamofire would not generate any errors. But assuming you don't have access to the server code and you need to parse an empty response as correct then you could use the following code:
func testRequestWithAlamofire() {
let dataResponseSerializer = DataResponseSerializer(emptyResponseCodes: [200, 204, 205]) // Default is [204, 205] so add 200 too :P
AF.request("http://www.mocky.io/v2/5aa696133100001335e716e0", method: .put).response(responseSerializer: dataResponseSerializer) { response in
switch response.result {
case .failure(let error):
print(error)
case .success(let value):
print(value)
}
}
}
And for a real and complete example of how async/await from Alamofire or any other async context look this code:
// This function get report from API and save to a local JSON to be readed by the app
func updateReport() {
Task {
guard let session = self.sessionRepository.getSession(WithUser: Defaults.lastLoggedUsername!) else { return }
guard let company = session.profile?.companies.first else { return }
self.apiManager.configure(WithToken: session.accessToken)
do {
let dateA = Date().dateAtStartOf(.year)
//let dateB = Date().dateAtEndOf(.month)
let dateB = Date() // Just now
let report = try await self.apiManager.report(CompanyId: company._id, DateA: dateA, DateB: dateB, ChartPeriodicity: .month)
self.currentReport = report
// Save data to disk to be read later
self.reportManager.saveReportToDisk(report: report!, withProfileId: session.profile!._id)
} catch {
print("Error getting report: \(error)")
}
}
}
// Get personal report from a given date range
func report(CompanyId companyId: String, DateA dateA: Date, DateB dateB: Date, ChartPeriodicity chartPeriodicity: ChartPeriodicity) async throws -> CDReport? {
try await withCheckedThrowingContinuation { continuation in
self.contappApi.request(.report(companyId: companyId, dateA: dateA, dateB: dateB, chartPeriodicity: chartPeriodicity)) { result in
switch result {
case let .success(response):
// Check status code
guard response.statusCode == 200 else {
continuation.resume(throwing: ContappNetworkError.unexpected(code: response.statusCode))
return
}
// Decode data
do {
//let report = try JSONDecoder().decode(CDReport.self, from: response.data)
let report = try CDReport(data: response.data)
continuation.resume(returning: report)
} catch {
continuation.resume(throwing: ContappNetworkError.cantDecodeDataFromNetwork)
}
case .failure(_):
continuation.resume(throwing: ContappNetworkError.networkError)
}
}
}
}
Alamofire already supports this, you just need to choose a form. Your biggest issue will be accepting a 200 with no data, as that's technically invalid since only 204 or 205 are supposed to be empty.
All Alamofire responses require some sort of payload type, but Alamofire provides an Empty type to fill this role for Decodable. So the simplest way is to use the
await AF.request(...)
.serializingDecodable(Empty.self, emptyResponseCodes: [200])
.response
Note, if you already have an Empty type or are importing Combine in the same file as this code, you may need to disambiguate by using Alamofire.Empty.
If Alamofire does not provide a method for your purpose, then you will have wrap the old Alamofire methods that uses closures as below:
func myRequest() async throws {
try await withUnsafeThrowingContinuation { continuation in
myAlamofireRequest {
continuation.resume()
}
}
}
I am currently writing an Alamofire HTTP request and am running into an issue where my view is not loading - likely because there is no data. The confusing part is that this was working yesterday. In the request I was able to do print(data) and the result was 506 bytes which, if my calculation is correct, is about the correct size given the JSON payload returned from the endpoint below.
#State var recipes = [Recipe]()
AF.request("http://localhost:3000/recipes").responseJSON { response in
guard let data = response.data else { return }
if let response = try? JSONDecoder().decode([Recipe].self, from: data) {
DispatchQueue.main.async {
self.recipes = response
}
return
}
}
I can confirm that the endpoint that is being hit returns the following data...
[
{
"name":"Manhattan",
"image":"https://images.unsplash.com/photo-1536935338788-846bb9981813?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2486&q=80",
"spirit":"Bourbon",
"ice":"Crushed",
"glass":"Coupe",
"yield":"3.75",
"description":"This is a good drink. You should make it.",
"ingredients":[
{
"bottle":"High West Son Of Bourye",
"amount":"2.5"
},
{
"bottle":"Cocchi Vermouth Di Torino",
"amount":"0.75"
},
{
"bottle":"Simple Syrup",
"amount":"0.083"
}
]
}
]
I also have my Recipe and Ingredient model here which should be able to decode based on the above JSON.
struct Recipe: Decodable, Identifiable {
var id = UUID()
var name: String
var image: String
var spirit: String
var ice: String
var glass: String
var yield: String
var description: String
var ingredients: [Ingredient]
}
struct Ingredient: Decodable, Identifiable {
var id = UUID()
var bottle: String
var amount: String
}
Is anybody able to spot an issue? I was trying to put a debugging print in the DispatchQueue but it is not printing which, to me, sounds like an error. However I am new to Swift/XCode/iOS and am not sure the best debugging practices for this.
If you can't debug yourself, NEVER USE try?. With more experience, I'd say that we tend to not use try?, but sometimes we do. But when we write try?, we are able to find an possible issue, ie debug if needed.
Let's do a proper try then, with a do/catch:
do {
let response = try JSONDecoder().decode([Recipe].self, from: data
DispatchQueue.main.async {
self.recipes = response
}
} catch {
print("Oops, there was en error while decoding: \(error)") // and not error.localizedDescription as it's more for users than developpers, so you'll skip all the useful informations
}
And read the output.
Going further?
Don't believe what's the API is supposed to return.
I've seen plenty and plenty of questions where the returned values was an error message, a XML Error message, a JSON Error message, an HTML Error message, and a JSON value missing, or of bad type, etc. And that, your JSONDecoder wasn't expecting it...
Reasons could be various, from bad/missing parameters, bad/missing APIKey, server down, bad/missing header, etc.
But, then, print the returned value.
print(String(data: data, encoding: .utf8) ?? "No data found")
So print it directly when you get it, or at least in the catch:
} catch {
print("Oops, there was en error while decoding: \(error)") // and not error.localizedDescription as it's more for users than developpers, so you'll skip all the useful informations
print("While getting response stringified: \(String(data: data, encoding: .utf8) ?? "No data found")")
}
If you don't understand the error message output, it's okay, there is no shame about it. But your first job is to get that error message. You can share it on SO if you don't understand it, you might get help with that. But currently, we can't guess what's wrong with your code.
It's a good idea to drop some clues in your code when looking for a failure.
If it were me I'd do something like this:
AF.request("http://localhost:3000/recipes").responseJSON { response in
guard let data = response.data else {
print("Error trying to receive data in ", #file, #function)
return
}
do {
let response = try JSONDecoder().decode([Recipe].self, from: data) {
DispatchQueue.main.async {
self.recipes = response
}
} catch {
print("Error failed to decode json data with error: \(error) in \(#file)", #function)
}
}
Trying to wrap Codable response data from WebService
used cocoa pod
pod 'WebService', '~> 0.4'
var webServiceObject : WebService = WebService()
webServiceObject.sendRequest(URLString, parameters: parameters as NSDictionary?, requestType: .post, success: {response,data in
do{
// Handle data when request Success
let jsonDecoder = JSONDecoder()
do {
let result = try jsonDecoder.decode([ResponseModelValue].self, from: data as! Data)
print(response?.statusCode as Any)
print("Success Response Value", result)
}
} catch {
print(error)
}
}, failed: {
(response : HTTPURLResponse?, ResponseDict : Any?) in
print(response?.statusCode as Any)
print("Failed Response Dictionary", ResponseDict!)
// Handle data when request fails
}, encoded: false )
I got following error
Could not cast value of type '__NSDictionaryI' (0x10c81ab38) to 'NSData' (0x10c819620).
Can any one suggest me what I done wrong here, so I can parse proper response data with codable [ResponseModelValue]
After looking at that library, it appears that it will turn the response into a dictionary if possible and fall back to returning data.
https://github.com/kansaraprateek/WebService/blob/master/WebService/Classes/WebService.swift#L281
It doesn't appear like there is a way to configure that behaviour. The library was also created before Codable was created. You may need to reach out the library owner for a fix, fork the repo and fix it yourself, or switch to using URLSession since the code you posted is mostly the same as a URLSession call.
I want to use JSON data in my app. So I am using this webservice calling method to convert my json data to an array.
func getData(path: String, completion: (dataArray: NSArray)->()) {
let semaphore = dispatch_semaphore_create(0)
// var datalistArray = NSArray()
let baseUrl = NSBundle.mainBundle().infoDictionary!["BaseURL"] as! String
let fullUrl = "\(baseUrl)\(path)"
print("FULL URL-----HTTPClient \(fullUrl)")
guard let endpoint = NSURL(string:fullUrl) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(URL: endpoint)
NSURLSession.sharedSession().dataTaskWithRequest(request,completionHandler: {(data,response,error) in
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSArray else {//NSJSONReadingOptions.AllowFragments
throw JSONError.ConversionFailed
}
print(json)
if let data_list:NSArray = json {
completion(dataArray: data_list)
dispatch_semaphore_signal(semaphore);
}
}catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}) .resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
But now my service sending json data within xml tags like <string xmlns="http://tempuri.org/">json data</string so I am getting an exception when I try to convert my json data. The exception is this.
Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}
What should I change in my code to remove those tags before sending to json parser?
Please help me.
Thanks
I think your response that you get from server is in xml format not in json. If it is in xml format then you must do xml parsing instead of json parsing.
NSJSONSerialization.JSONObjectWithData is json parsing that give json object from data (data in json format).
But if you getting response in xml format from server then you should use NSXMLParser to parse the data.
If you don't have much idea about it then you can refer tutorial like
XML Parsing using NSXMLParse in Swift by The appguruz or can use third party libraries.
I have a problem I want to handle a JSON Response with Alamofire in Swift
So I found this answer on Stackoverflow unfortunately this post is a few days older.
My question is how can I receive the data from Alamofire on first button press (without swiftyJSON).
I hope someone could help me.
This is the link I found on Stackoverflow.
Handle JSON Response with Alamofire in Swift
this is a small example.
This is what the JSON returns if it fails json_file.json
{ "transaction":"error" }
This is what the JSON returns if its success json_file.json
{ "transaction":"success" }
this is the code , you must add your own URL that will return any of those json responses. (example only)
Alamofire.request(.GET, "http://myjsonexamplewebsite.com/json_file.json", parameters:nil)
.responseJSON { (_, _, JSON, _) in
//println(JSON)
var response = JSON as NSDictionary
var transaction = response.objectForKey("transaction") as String
if transaction == "success" {
NSLog("JSON response was successfull")
}
else {
NSLog("JSON response had an Error")
}
}