JSON POST Request with Alamofire and EVReflection - ios

I"m trying to do a simple POST request in swift. I'd like to post an array of objects (alarms) and I"m constantly running into problems. Here is what I have so far:
func submitAlarms(alarms: [Alarm],onCompletion: #escaping ([Alarm]) -> Void){
let route = baseURL
let token = SessionManager().storedSession!.token.idToken
let headers = [
"Authorization": "Bearer \(token)"
]
let parameters = [
"alarms": alarms.toJsonString()]
print("Parameters ", parameters)
Alamofire.request(route, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.validate()
.responseJSON{ response in
switch response.result{
case .failure(let error):
print("POST Alarm JSON Error: \(error)")
case .success(let value):
let json = JSON(value)
let alarms_json = json["alarms"]
print(alarms_json)
let alarms_string = alarms_json.rawString()
let alarms: [Alarm] = [Alarm](json: alarms_string)
//let alarms = [Alarm](json: res_string)
onCompletion(alarms)
}
}
}
I"m mainly having issues with the parameters part. For whatever reason, Alamofire can't seem to take an array of objects, so I created a dictionary with a top level alarm key that holds the array of alarms. I then use EVReflection to convert my alarm array to a json string. But that conversion gives me this:
Parameters ["alarms": "[\n{\n \"isActive\" : true,\n \"volume\" : 10,\n \"minute\" : 15,\n \"days\" : [\n 0,\n 1,\n 2,\n 3,\n 4,\n 5,\n 6\n ],\n \"brightness\" : 10,\n \"hour\" : 6,\n \"audio\" : 4,\n \"duration\" : 1,\n \"label\" : \"Alarm\",\n \"allowSnooze\" : false,\n \"isSynchronized\" : false\n}\n]"]
Where are all these extra \ and \n coming from? Using Alamofire, the server responds with a 400 because these extra pieces make it invalid JSON. What am I missing here? I am using Xcode 8/Swift 3, with the latest Alamofire and EVReflection. I"ve looked at other examples, but they are mostly out of date. A lot of them also use extensions and custom encoding, which seems ridiculous for such a simple request. Appreciate any help and bonus points if you can clean up the response as well.

For creating the parameters you are doing a:
let parameters = ["alarms": alarms.toJsonString()]
Which means that you will get 1 paramater that will contain the json as a string.
EVReflection will first convert the alarms object to a dictionary and then convert it to json using the Apple standard function:
JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
Since it's using the .prettyPrinted option it will be formatted in a nice readable format. This means that it will add /n for where there should be enters plus spaces for making a nice layout.
In your case you have to look at the api that you are calling to see what you should change in your code. Currently you are doing a http get. Then the parameters should be simple key - value pairs. You could use the EVReflection toDictionary function for that if you want to post all your object properties. But in your case I think you need to do a post (see Alamofire documentation) and then you can put the json in the request body.
P.S. I just found this issue by accident. You are welcome to also create issues at https://github.com/evermeer/EVReflection or ask questions at https://gitter.im/evermeer/EVReflection Then i will receive a notification for it.

Related

Alamofire Passing Parameter With Common Keys and Multiple Values?

I need to do this in my project:
I can do this easily if I manually append strings to my URL in Alamofire, but I don't want that. I want the parameters as Parameter object.
multiple values in one common key for parameter
What I've been doing:
public func findCreate(tags: [String], withBlock completion: #escaping FindCreateServiceCallBack) {
/* http://baseurlsample.com/v1/categories/create_multiple?category_name[]=fff&category_name[]=sss */
let findCreateEndpoint = CoreService.Endpoint.FindMultipleCategories
let parameters: Parameters = ["category_name[]" : tags]
Alamofire.request(
findCreateEndpoint,
method: .post,
parameters: parameters,
encoding: URLEncoding(destination: .queryString),
headers: nil
).responseJSON { (response) in
print(response)
}
//....
}
The current result if I run this is okay but the values sent to the server has [" "]. For example:
["chocolate"]
Again, the questions are, in which part of my whole code I'm wrong? How can I send parameters like the above that have one common key and multiple values?
I also have tried adding encoding option to the Alamofire.request() If I add encoding: JSONEncoding.prettyPrinted or encoding: JSONEncoding.default I get status code 500.
Some links that have the same question but no exact answers, I always see posts that have answers like using a custom encoding and that's it.
https://github.com/Alamofire/Alamofire/issues/570
Moya/Alamofire - URL encoded params with same keys
Additional info:
This works, but I need to send multiple String:
let parameters: [String : Any] = ["category_name[]" : tags.first!]
And this works as well:
Alamofire.request("http://baseurlsample.com/v1/categories/create_multiple?category_name[]=fff&category_name[]=sss", method: .post).responseJSON { (data) in
print(data)
}
You don't need a custom encoding for this format.
You can send parameters encoded like this:
category_name[]=rock&category_name[]=paper
By using URLEncoding (which you're already doing) and including the multiple values that should have the same key in an array:
let parameters: Parameters = ["category_name": ["rock", "paper"]]
It'll add the [] after category_name for you, so don't include it when you declare the parameters.

Swift Dictionary adding square brackets after key for a value of type [String]

I have a serializer method that turns my swift objects into dictionaries so that I can send them in http requests.
this particular method is the one giving me the problem
class func toDictionary ( _ order: Order ) -> Dictionary<String, Any> {
return [
"products" : NSArray(array:order.getProducts()),
"owning_user" : NSString(string: order.getOwningUser()),
"restaurant" : NSString(string: order.getRestaurantId())
]
}
order.getProducts() returns an array of type [String]
When I send this in a http request it gets sent as
{"products[]":["...","..."],
"restaurant":"sdfsdf"
}
Obviously my server is expecting products as the key therefore its not getting the proper values.
Any idea why the square brackets are being added?
Note:
My http requests are done via alamofire
EDIT:
Turns out the problem was with alamofire
Please see below for solution
Turns out this is a problem to do with alamofire's encoding when passing a dictionary as a httpBody.
For anyone having the same problem:
solve this by adding the following property to your Alamofire post request
encoing: JSONEncoding.default
the final request looks as follows
Alamofire.request( requestURL, method: .post, parameters: orderJson, encoding: JSONEncoding.default)
and it yields the following JSON being posted
{ restaurant: '580e33ee65b84973ffbc7beb',
products: [ '580f5cdafaa1067e55be696d' ],
owning_user: '580e2d174e93b0734e9a04cb'
}
As I had originally wanted.
Ok this appears to work here, so I think I need more context as to what your doing different. If this solves your issue, please up vote! Thanks!
Possible issues; you may be have an array within another array? If order.getProducts() already returns an array, don't place it in another. Another option may be to .flatMap it "NSArray(array:order.getProducts()).flatMap {$0}" << will make a single array out of arrays of arrays.
//: Playground - noun: a place where people can play
import UIKit
func toDictionary () -> Dictionary<String, Any> {
return [
"products" : NSArray(array:["Paper","Pencil","Eraser"]),
"owning_user" : NSString(string: "user2976358"),
"restaurant" : NSString(string: "TacoBell")
]
}
let rValue = toDictionary()
let jsonData:Data!
do {
jsonData = try JSONSerialization.data(withJSONObject: rValue, options: .prettyPrinted)
let newString = String(data: jsonData, encoding: .utf8)
print(newString!)
} catch
{
print(error)
}
The results in the Debug area show this
{
"restaurant" : "TacoBell",
"products" : [
"Paper",
"Pencil",
"Eraser"
],
"owning_user" : "user2976358"
}

Alamofire 4 Request is returning NSArray, cannot figure out how to parse with SwiftyJSON in Swift 3

The previous version of this Alamofire .POST request and SwiftyJSON parsing was working fine with Swift 2.2 and Xcode 7. Updated to Swift 3.0, which required install of updated Alamofire (4.0.0) and updated SwiftyJSON. After some syntax adjustments, everything now compiles.
The problem is that my web app now appears to return an NSArray, whereas before, when the code worked, a nearly identical Request got an NSData return that SwiftyJSON would parse. The following code shows the Request:
Alamofire.request("https://www.mywebappurl", method: .post, parameters: parameters)
.responseJSON { (response:DataResponse<Any>) in
if let data = response.result.value as? Data {
The data variable is never assigned because the response type is not NSData. Tried to cast to that type by changing the last line to this:
let data = response.result.value as! Data
That version compiles fine, but as soon as you trigger the Request you get an error: Could not cast value of type '__NSArrayI' (0x105a37c08) to 'NSData'
Note that the request is returning data as expected. And in the previous Alamofire this data was NSData without any action being taken to convert it. Since it appears from the aforementioned error that the returned data is an array already, has it already been parsed by Alamofire? Or is there something that can be done to make SwiftyJSON parse it like it was parsed before?
EDIT
Since the current type being returned is an NSArray, and the web app is sending an array, it seems possible that the SwiftyJSON parsing is no longer necessary? Tried the following code:
Alamofire.request("https://www.mywebappurl", method: .post, parameters: parameters)
.responseJSON { (response:DataResponse<Any>) in
let testdata = response.result.value as! NSArray
print(testdata[0])
Which yielded this output in the Xcode "All Output" screen:
{
1 = "08/01/16";
2 = 285;
3 = 160;
}
It's not clear to me if that means testdata is an array of arrays, an array of dictionaries, or an array of unparsed strings. So as an alternative to answering the question of "How can one obtain an NSData response from this Alamofire request", a response to the question of "What one line of code, if any, can be used to obtain the integer value 285 from the NSArray testdata shown above?" would also resolve the problem.
If you change the data initialization line from this:
if let data = response.result.value as? Data {
to this:
let data = response.data
Then the request results in an NSData return that can be parsed by SwiftyJSON as it was before. The working request and parsing code looks like this:
Alamofire.request("https://www.mywebappurl", method: .post, parameters: parameters)
.responseJSON { (response:DataResponse<Any>) in
let data = response.data
let jsonvalues = JSON(data: data!)
Some error handling might be appropriate to add, but that's not related to the question at hand.

Send an array as a parameter in a Alamofire POST request

My app is currently using AWS API Gateway and Alamofire to access different lambda functions that act as my backend.
I have the need to send an array as one of the parameters to one of those API end points, for that I am using the following code:
var interests : [String]
interests = globalInterests.map({ (interest) -> String in
return interest.id!
})
// Parameters needed by the API
let parameters: [String: AnyObject] = [
"name" : name,
"lastName" : lastName,
"interests" : interests
]
// Sends POST request to the AWS API
Alamofire.request(.POST, url, parameters: parameters, encoding: .JSON).responseJSON { response in
// Process Response
switch response.result {
case .Success:
print("Sucess")
case .Failure(let error):
print(error)
}
}
But that is not working because of the array is not being recognized by the API, but if I create a "static" array
let interests = ["a", "b", "c"]
Everything works as it is supposed to.
How can I fix this situation given that the array of interests come from another part of the code, how should I declare it or construct it?
A friend managed to accomplish this in Android using an
ArrayList<String>
EDIT:
Printing the parameters array shows me this:
["name":test, "interests": <_TtCs21_SwiftDeferredNSArray 0x7b05ac00>( 103, 651, 42), "lastName": test]
By using NSJSONSerialization to encode JSON, you can build your own NSURLRequest for using it in Alamofire, here's a Swift 3 example:
//creates the request
var request = URLRequest(url: try! "https://api.website.com/request".asURL())
//some header examples
request.httpMethod = "POST"
request.setValue("Bearer ACCESS_TOKEN_HERE",
forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Accept")
//parameter array
let values = ["value1", "value2", "value3"]
request.httpBody = try! JSONSerialization.data(withJSONObject: values)
//now just use the request with Alamofire
Alamofire.request(request).responseJSON { response in
switch (response.result) {
case .success:
//success code here
case .failure(let error):
//failure code here
}
}
}
AnyObject can only represent class type. In Swift, Array and Dictionary are struct, instead of the class type in many other languages. The struct cannot be described into AnyObject, and this is why Any comes in. Besides of class, Any can be utilized in all other types too, including struct and enum.
Therefore whenever we type cast array or dictionary to AnyObject _TtCs21_SwiftDeferredNSArray error occurs.So we have to Any instead of AnyObject.
let parameters: [String: Any] = [
"name" : name,
"lastName" : lastName,
"interests" : interests
]
The problem is that you have just declared the array and not initialized it. That makes the interest array as nil even if u try to insert the data. Try writing
var interests = [String]()
instead of
var interests : [String]
let ValueArray = ["userid": name,"password":password]
pass ValueArray [parameters: ValueArray] also verify the encoding accepted by the API.
It turned out to be a problem with duplicated ids in the array. The code behind the API threw an exception that was not being send back as an error.
All the other answers are correct, I tested them after finding the problem and everything worked so I am going to up-vote them.
Thank you very much.

Response JSONString order not correctly after reading in iOS

I have JSONString from api as bellow:
[JSONString from api]
But after I read in iOS from Alamofire the order of the JSONString is not correct as api:
[JSON After read in iOS]
How can I keep the JSON format the same order as api?
As explained by #Nimit, JSON format represented in your callback and the API response is of least concern. What you need to care about is that when you are accessing the values from the response, the KEY should be same as seen in the API. No mismatch, not even of the case-sensitive letter, or you will always get the NIL in the response.
To explain it better to you with the use of Alamofire, let's me show you one example:
let APIURL = "https://api.yoururl.com"
Alamofire.request(.GET, APIURL , headers: headers) .responseJSON { response in
let value = response.result.value!
let JSONRes = JSON(value)
let KLValue = JSONRes["Kuala Lumpur"].int!
print(KLValue) //Or call a function to handle the callback
}
Here I am using SwiftyJSON for JSON. In the end, all you want to do is get the data out of the associated keys in the JSON response, no need to worry about how they have been formatted, or what's the order of Keys in the response - most of the time you will get the same as in the API - but in case it changes, need not to worry.
On the another front, to be sure that nothing happens to your app when JSON fields are nil, always put an if-let like this:
if let valueFromJSON = JSONRes["Kuala Lumpur"].string {
someVariable = valueFromJSON
} else {
someVariable = "No Value"
}
Thanks!
You can't do it, unless you write your own JSON parser. Any self-respecting JSON library won't guarantee you the order, if it wants to conform to the JSON spec.
From the definition of the JSON object:
the NSDictionary class represents an unordered collection of objects;
however, they associate each value with a key, which acts like a label
for the value. This is useful for modeling relationships between pairs
of objects.
If you have a jsonObject, such as data, then you can convert to json string like this:
let jsonString = JSONSerialization.data(withJSONObject: data,
options: JSONSerialization.WritingOptions.sortedKeys)
when you use sortedKeys option, the json will be specifies that the output sorts keys in lexicographic order.

Resources