I tried a long time to handle a JSON response with SwiftyJSON, but I don't know how to parse the response. Here's my code:
var jsonString:String = ""
Alamofire
.request(.GET, url + "/HMServer/rest/administration/version")
.responseJSON {
(request, response, data, error) -> Void in
let json = JSON(object: data!)
//here I want to do something with parsing
}
The requests I do with Alamofire and get back a JSON response. The response look like:
[message: [SERVER_VERSION: 0.1, INTERFACE_VERSION: 0.1], type: success]
I want to save all elements in strings and give them back. How can I parse the JSON response saved in the let let json? I tried to use Alamofire-SwiftJSON but the code does not work. All examples I found are too old because the SwiftyJSON code was refactored a few days ago.
THX!
I have fixed Alamofire-SwiftJSON's issue,
but you can do it by yourself in responseJSON's closure like:
Alamofire.request(.GET, url + "/HMServer/rest/administration/version")
.responseJSON { (request, response, data, error) -> Void in
if error != nil {
self.swiftyJSON = SwiftyJSON.JSON.Null(error)
} else if object != nil {
self.swiftyJSON = SwiftyJSON.JSON(object: object!)
} else {
self.swiftyJSON = SwiftyJSON.JSON.Null(nil)
}
}
Above code is not like Alamofire-SwiftJSON in the global queue, the initialization (AnyObject to SwiftyJSON) is running in the main queue.
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 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.
I'm trying to use the Yoda API and send a request using the Alamofire Swift framework. I know that the API is correctly working, as I have tested the endpoint with my Mashape API key multiple times. I can also see that the requests are being sent (homepage of Mashape under my application). However my JSON response is always nil.
func handleRequest(words:String){
var saying = words.stringByReplacingOccurrencesOfString(" ", withString: "+");
saying = "?sentence=" + saying;
let url = NSURL(string: (baseURL+saying));
println(url);
var response:String;
Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders = additionalHeaders;
Alamofire.request(.GET, url!).responseJSON { (_, _, JSON, _) in
println(JSON);
}
}
The words string can be "This is my first sentence" and it will automatically replace the spaces with "+" as per the API spec. Please Ignore the multiple println statements, they are just for debugging.
This is just proof of concept code, its purposely not doing much error checking and isn't pretty for that reason. If you have any suggestions I would appreciate them.
For some reason it's an issue I've too with the Alamofire request for JSON. It is the way I handle the JSON requests using Alamofire :
Alamofire.request(.GET, urlTo, parameters: nil, encoding: .URL).responseString(completionHandler: {
(request: NSURLRequest, response: NSHTTPURLResponse?, responseBody: String?, error: NSError?) -> Void in
// Convert the response to NSData to handle with SwiftyJSON
if let data = (responseBody as NSString).dataUsingEncoding(NSUTF8StringEncoding) {
let json = JSON(data: data)
println(json)
}
})
I strongly recommend you using SwiftyJSON to manage the JSON in a better and easy way, it's up to you.
I hope this help you.
Alamofire request have several method for handle response. Try to handle data response and convert it to String. Confirm that response JSON is normal.
Alamofire.request(.GET, url!).response { (_, _, data, error) in
let str = NSString(data: data, encoding: NSUTF8StringEncoding)
println(str)
println(error)
}
Also checkout error while parsing JSON data.
I have the HTTP code in an AngularJS controller:
$http.post('/api/users/authenticate', {email: $scope.email, password: $scope.password})
.success(function (data, status, headers, config) {
authService.login($scope.email);
$state.go('home');
})
.error(function (data, status, headers, config) {
$scope.errorMessages = data;
$scope.password = "";
});
In the success case, the server will respond with a JSON representation of a user. In the error case the server will respond with a simple string such as User not found which can be accessed through the data parameter.
I'm having trouble figuring out how to do something similar in Alamofire. Here's what I have right now:
#IBAction func LoginPressed(sender: AnyObject) {
let params: Dictionary<String,AnyObject> = ["email": emailField.text, "password": passwordField.text]
Alamofire.request(.POST, "http://localhost:3000/api/users/authenticate", parameters: params)
.responseJSON {(request, response, data, error) in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
let welcome = self.storyboard?.instantiateViewControllerWithIdentifier("login") as UINavigationController;
self.presentViewController(welcome, animated: true, completion: nil);
})
}
else{
dispatch_async(dispatch_get_main_queue(), {
// I want to set the error label to the simple message which I know the server will return
self.errorLabel.text = "something went wrong"
});
}
}
}
I have no idea if I'm handling the non-error case correctly either and would appreciate input on that as well.
You are are on the right track, but you are going to run into some crucial issues with your current implementation. There are some low level Alamofire things that are going to trip you up that I want to help you out with. Here's an alternative version of your code sample that will be much more effective.
#IBAction func loginPressed(sender: AnyObject) {
let params: [String: AnyObject] = ["email": emailField.text, "password": passwordField.text]
let request = Alamofire.request(.POST, "http://localhost:3000/api/users/authenticate", parameters: params)
request.validate()
request.response { [weak self] request, response, data, error in
if let strongSelf = self {
let data = data as? NSData
if data == nil {
println("Why didn't I get any data back?")
strongSelf.errorLabel.text = "something went wrong"
return
} else if let error = error {
let resultText = NSString(data: data!, encoding: NSUTF8StringEncoding)
println(resultText)
strongSelf.errorLabel.text = "something went wrong"
return
}
var serializationError: NSError?
if let json: AnyObject = NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments, error: &serializationError) {
println("JSON: \(json)")
let welcome = self.storyboard?.instantiateViewControllerWithIdentifier("login") as UINavigationController
self.presentViewController(welcome, animated: true, completion: nil)
} else {
println("Failed to serialize json: \(serializationError)")
}
}
}
}
Validation
First off, the validate function on the request will validate the following:
HTTPStatusCode - Has to be 200...299
Content-Type - This header in the response must match the Accept header in the original request
You can find more information about the validation in Alamofire in the README.
Weakify / Strongify
Make sure to weak self and strong self your closure to make sure you don't end up creating a retain cycle.
Dispatch to Main Queue
Your dispatch calls back to the main queue are not necessary. Alamofire guarantees that your completion handler in the response and responseJSON serializers is called on the main queue already. You can actually provide your own dispatch queue to run the serializers on if you wish, but neither your solution or mine are currently doing so making the dispatch calls to the main queue completely unnecessary.
Response Serializer
In your particular case, you don't actually want to use the responseJSON serializer. If you do, you won't end up getting any data back if you don't pass validation. The reason is that the response from the JSON serialization is what will be returned as the AnyObject. If serialization fails, the AnyObject will be nil and you won't be able to read out the data.
Instead, use the response serializer and try to parse the data manually with NSJSONSerialization. If that fails, then you can rely on the good ole NSString(data:encoding:) method to print out the data.
Hopefully this helps shed some light on some fairly complicated ways to get tripped up.
So Alamofire treats all requests successful. This really comes down to the API server http headers being returned.
You could use Alamofire.Request.validate()
It'll allow you to validate http headers, etc. Check out the example
https://github.com/Alamofire/Alamofire#validation
I am assuming the the error message will be in the data object.
to access the values from data you could do something like
I am not really sure about your api response looks but in this example
{
"message": "Could not authenticate"
}
let message: String? = data?.valueForKey("message") as String
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")
}
}