Swift JSONEncoding problem with encoding a list of dictionaries - json

I am having problems with sending a list of dictionaries as the value for one of my keys in a dictionary. I'm sending a dictionary that represents a game, with one of the key value pairs being a list of points. So for my 'points' key the corresponding value is a list of dictionaries.
var allPoints : [[String: String] = []
for playerScoreTracker in playerScoreTrackers!{
for point in playerScoreTracker.points{
let tempDict : [String : String] = ["type": point.typeOfPoint.rawValue, "scorer" : point.scorer.uuid]
allPoints.append(tempDict)
}
}
var parameters : Dictionary<String, Any> = [
"playerOne": playerOne.uuid,
...
"team_two_score": scoreboard.teamTwoScore,
"confirmed": false,
"points": allPoints
]
The problem is that when I send this dictionary using Alamofire (which uses JSONEncoding.default to my knowledge) it ends up sending something different than I want. My Alamofire send function looks like this
static func post<T: Decodable>(url: String, parameters: [String: Any], returnType: T.Type, completion: #escaping (Bool, Any) -> Void) {
AF.request(url, method: .post, parameters: parameters, headers: getHeaders()).responseDecodable(of: returnType.self) { response in
guard let returnStatusCode = response.response?.statusCode else {
let returnData : [String] = ["No connection."]
completion(false, returnData)
return
}
switch returnStatusCode {
case 200, 201:
let returnData = response.value!
completion(true, returnData)
case 400:
var errorsDict : [String: [String]] = [:]
do {
errorsDict = try JSONDecoder().decode(Dictionary<String, [String]>.self, from: response.data!)
} catch {
errorsDict["my_side_errors"] = ["Couldn't decode json response."]
}
let errors : [String] = getErrorsFromParams(params: parameters, errorsDict: errorsDict)
completion(false, errors)
default:
let errors : [String] = ["Error."]
completion(false, errors)
}
}
}
For some reason when it encodes my parameters it sends them like this...
{
'confirmed': ['0'],
'playerFour': ['8b52d186-ab60-438e-92f1-a3d35430ea81'],
'playerOne': ['f33e09b5-be8b-49e7-99c2-1e2df7512a71'],
'playerThree': ['583e0143-7352-4907-a806-a3b16baceefc'],
'playerTwo': ['24da0653-7fbc-41c3-8a47-bdefb9323eb2'],
'points[][type]': ['tk', 'tk'],
'points[][scorer]': ['8b52d186-ab60-438e-92f1-a3d35430ea81', '8b52d186-ab60-438e-92f1-a3d35430ea81'],
'team_one_score': ['12'],
'team_two_score': ['0'],
'time_ended': ['2021-09-11 10:54:00'],
'time_started': ['2021-09-11 10:53:57']
}
The list of dictionaries I send as points is being split up into two lists, one list for each key in the array.
If I send the same JSON in Postman, it works fine and exactly how I want it to. This is the JSON I send it in Postman...
{
"playerOne": "f33e09b5-be8b-49e7-99c2-1e2df7512a71",
"playerTwo": "99028402-abdd-42dd-a8d9-48c05ba2787e",
"type": "pu",
"team_two_score": 0,
"time_started": "2021-09-11 12:33:44",
"time_ended": "2021-09-11 12:33:48",
"points": [
{"scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71", "type": "tk"},
{"scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71", "type": "tk"},
{"type": "tk", "scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71"},
{"type": "tk", "scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71"},
{"type": "tk", "scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71"},
{"scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71", "type": "tk"}
],
"playerThree": "24da0653-7fbc-41c3-8a47-bdefb9323eb2",
"playerFour": "583e0143-7352-4907-a806-a3b16baceefc",
"team_one_score": 12,
"confirmed": false
}
This JSON being sent works fine with my backend. I cannot for the life of me figure out how to get Swift to encode my params in this way.
Here's a printout of the parameters right before I send them.
params: [
"team_one_score": 13,
"points": [
["type": "tk", "scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71"],
["scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71", "type": "tk"],
["type": "tk", "scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71"],
["type": "tk", "scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71"],
["type": "tk", "scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71"],
["scorer": "f33e09b5-be8b-49e7-99c2-1e2df7512a71", "type": "sk"]
],
"time_started": "2021-09-11 12:12:26",
"confirmed": false,
"time_ended": "2021-09-11 12:12:30",
"playerThree": "583e0143-7352-4907-a806-a3b16baceefc",
"team_two_score": 0,
"playerTwo": "24da0653-7fbc-41c3-8a47-bdefb9323eb2",
"playerFour": "8b52d186-ab60-438e-92f1-a3d35430ea81",
"playerOne": "f33e09b5-be8b-49e7-99c2-1e2df7512a71"
]
Here's the output from cURLDescription
$ curl -v \
-X POST \
-H "User-Agent: DayOfDie/1.0 (aberard.DayOfDie; build:1; iOS 14.5.0) Alamofire/5.4.1" \
-H "Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8" \
-H "Accept-Language: en;q=1.0" \
-H "Authorization: Token be1d6e220931d0249e3d600ef9682909f39252ec" \
-H "Content-Type: application/x-www-form-urlencoded; charset=utf-8" \
-d "confirmed=0&playerFour=8b52d186-ab60-438e-92f1-a3d35430ea81&playerOne=f33e09b5-be8b-49e7-99c2-1e2df7512a71&playerThree=583e0143-7352-4907-a806-a3b16baceefc&playerTwo=24da0653-7fbc-41c3-8a47-bdefb9323eb2&points%5B%5D%5Btype%5D=sk&points%5B%5D%5Bscorer%5D=f33e09b5-be8b-49e7-99c2-1e2df7512a71&points%5B%5D%5Btype%5D=tk&points%5B%5D%5Bscorer%5D=f33e09b5-be8b-49e7-99c2-1e2df7512a71&points%5B%5D%5Btype%5D=tk&points%5B%5D%5Bscorer%5D=f33e09b5-be8b-49e7-99c2-1e2df7512a71&points%5B%5D%5Bscorer%5D=f33e09b5-be8b-49e7-99c2-1e2df7512a71&points%5B%5D%5Btype%5D=tk&points%5B%5D%5Bscorer%5D=f33e09b5-be8b-49e7-99c2-1e2df7512a71&points%5B%5D%5Btype%5D=tk&team_one_score=11&team_two_score=0&time_ended=2021-09-11%2012%3A18%3A55&time_started=2021-09-11%2012%3A18%3A51" \
"http://127.0.0.1:8000/games/"
Larme figured it out. For some reason or another my alamofire request was encoding it using url encoding instead of json encoding. I just added "encoding: JSONEncoding.default" to my request params and everything works fine now.

Related

JSON Array Swift send using Alamofire

I am trying to send an array of JSON object which I need to update object on my server. I need the array for params request.
This is my construction of array I need in order to update server object value:
[{
"propName":"number", "value":numberValue
}]
Here is what I am trying to make in Swift :
let params = [
{ "propName":"number", "value":numberValue },
{ "propName":"address", "value":addressValue },
{ "propName":"notes", "value":notesValue },
{ "propName":"latitude", "value":latitudeValue },
{ "propName":"longitude", "value":longitudeValue}
] as [String: Any]
let updateParkingSpotRequest = AF.request(URLs.MarkParkingSpotAsAvail(parkingSpotId: parkingSpotId), method: .patch, parameters: params, encoding: JSONEncoding.prettyPrinted, headers: nil, interceptor: nil, requestModifier: nil)
But it doesn't work since it cannot convert this form of data, XCode says: "Cannot convert value of type '[() -> String]' to type '[String : Any]' ". How can I get that format of Data which server needs?
You need to write the initializer like this:
let params = [
[ "propName": "number", "value": numberValue ],
[ "propName": "address", "value": addressValue ],
[ "propName": "notes", "value": notesValue ],
[ "propName": "latitude", "value": latitudeValue ],
[ "propName": "longitude", "value": longitudeValue ]
] as [[String: Any]]
i.e. params is an Array of Dictionaries of String to Any.
The parameters parameter is a type of [String: Any] in Alamofire. So you need to create a parameter dictionary.
So assume that you have a list of Data models, you can create the params like below;
struct Data {
var propName: String
var value: Any
}
let values: [Data] = [
Data(propName: "number", value: numberValue),
Data(propName: "address", value: addressValue),
Data(propName: "notes", value: notesValue),
Data(propName: "latitude", value: latitudeValue),
Data(propName: "longitude", value: longitudeValue)
]
let params: [String: Any] = Dictionary(uniqueKeysWithValues: values.map{ ($0.propName, $0.value) })

How to get the data the server sent back from a reqwest http call?

I have a simple http server, that expects a POST of json data. It will send back the exact same json data, but with an extra 'received' field.
I can demonstrate this using curl:
$ curl -X POST -H "Content-Type: application/json" -d '{"id":1,"jsonrpc":"2.0","method":"getNext","params":[{"flavour":"apples"}]}' http://127.0.0.1:8008
{"params": [{"flavour": "apples"}], "received": "ok", "jsonrpc": "2.0", "id": 1, "method": "getNext"}
The http server was send some json, and it replied the same json but with the extra 'received' field.
I am trying to do the exact same in rust with reqwest.
use serde_json::{Value};
#[tokio::main]
async fn main() {
let url = "http://127.0.0.1:8008";
let data = r#"
{
"id": 1,
"jsonrpc": "2.0",
"method": "getNext",
"params": [ {
"flavour": "apple"
} ]
}"#;
let v: Value = match serde_json::from_str(data) {
Err(_) => panic!("this is a terrible mistake!"),
Ok(y) => y,
};
let client = reqwest::Client::new();
let res = client.post(url)
.json(&v)
.send()
.await;
println!("{:?}",res);
}
While this does make the call, and I can see the sent json in the server log, and i can see the server sends back the json, just like with curl... I don't know who to actually get at the data in Rust. All there seems to be is an Ok that the call worked ... but how to get the json the server sent back?
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
Running `/Users/x/RUST/projects/monitor/target/debug/monitor`
Ok(Response { url: "http://127.0.0.1:8008/", status: 200, headers: {"server": "BaseHTTP/0.3 Python/2.7.16", "date": "Fri, 29 May 2020 13:54:39 GMT", "content-type": "application/json"} })
This works.
use serde_json::{Value};
#[tokio::main]
async fn main() {
let url = "http://127.0.0.1:8008";
let data = r#"
{
"id": 1,
"jsonrpc": "2.0",
"method": "getNext",
"params": [ {
"flavour": "apple"
} ]
}"#;
let v: Value = match serde_json::from_str(data) {
Err(_) => panic!("this is a terrible mistake!"),
Ok(y) => y,
};
let client = reqwest::Client::new();
let result = client.post(url)
.json(&v)
.send()
.await;
if let Ok(response) = result {
if let Ok(text) = response.text().await {
let result: serde_json::Result<Value> = serde_json::from_str(&text);
if let Ok(j) = result {
println!("received: {}, flavour still: {}", j["received"], j["params"][0]["flavour"]);
} else {
panic!("Could not convert text to json");
}
} else {
panic!("Could not extract text from response");
}
} else {
panic!("No response from server");
}
}
Finished dev [unoptimized + debuginfo] target(s) in 1.93s
Running `target/debug/monitor`
received: "ok", flavour still: "apple"

How to make Swift JSON request params

I got a problem with making a request to a server, I got a parsing error, could you help me to make the request properly?
I need to make a request like this:
"{ \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"call\", \"params\": [ \"c36c5a835cf88e82f97dcfa5b74f53f4\",\"network.interface.wan\",\"status\", {} ] }"
My request:
["jsonrpc": "2.0", "id": 1, "method": "call", "params": [token, "network.interface.wan", "status", []]]
If you want to convert Dictionary as JSON String response then you can try like this way.
Edit: In JSON string with params array the last object is empty dictionary so you need to set last object of params array as [:] instead of []
let dic:[String:Any] = [
"jsonrpc": "2.0",
"id": 1,
"method": "call",
"params": [
"token",
"network.interface.wan",
"status", [:]
]
]
if let data = try? JSONSerialization.data(withJSONObject: dic),
let string = String(data: data, encoding: .utf8){
print(string)
}
Output
{\"method\":\"call\",\"jsonrpc\":\"2.0\",\"id\":1,\"params\":[\"token\",\"network.interface.wan\",\"status\",{}]}

Adding [[String:AnyObject]] as parameter on Alamofire call (Swift 2)

I have this JSON (this is a simplified version):
{
"from":0,
"location": [{
"city":"Barcelona"
}]
}
And i'm trying to do an Alamofire request with this JSON on parameters. So I have typed something like this:
let parameter = [
"from":0,
"location": [
["city": "Barcelona"]
]
]
But it seems Alamofire doesn't accept the parse from [{ }] to [[]] on parameters. I have read that one way to solve this is using a MutableURLRequest but i don't find any clear example.
Alamofire.request(.GET, "http://myurl", parameters: (params as! [String : AnyObject]), headers: nil).responseCollection { (response: Response<[Group], NSError>) in
switch response.result {
case .Success(let groups):
print("Success")
case .Failure(_):
print("Failure")
}
}
This code above works and I get a result from this Alamofire Request but ignores this:
"location": [ ["city": "Barcelona"]]
Could someone can help with that? Thank you so much.

(Swift) twilio post request to set attributes using alamofire

I am attempting to use Twilio's REST Api and Alamofire to set certain Attributes to a Channel when creating it (https://www.twilio.com/docs/api/ip-messaging/rest/channels#action-create)
let parameters : [String : AnyObject] = [
"FriendlyName": "foo",
"UniqueName": "bar",
"Attributes": [
"foo": "bar",
"bar": "foo"
],
"Type": "private"
]
Alamofire.request(.POST, "https://ip-messaging.twilio.com/v1/Services/\(instanceSID)/Channels", parameters: parameters)
.authenticate(user: user, password: password)
.responseJSON { response in
let response = String(response.result.value)
print(response)
}
Using that code, the response I received back was that a Channel was created with FriendlyName foo and UniqueName bar, but that Channel had no Attributes set.
Looking at the Alamofire github (https://github.com/Alamofire/Alamofire) I see that there's a way to send a POST Request with JSON-encoded Parameters. So I tried this:
let parameters : [String : AnyObject] = [
"FriendlyName": "foo",
"UniqueName": "bar15",
"Attributes": [
"foo": "bar",
"bar": "foo"
],
"Type": "private"
]
Alamofire.request(.POST, "https://ip-messaging.twilio.com/v1/Services/\(instanceSID)/Channels", parameters: parameters, encoding: .JSON)
.authenticate(user: user, password: password)
.responseJSON { response in
let response = String(response.result.value)
print(response)
}
When adding "encoding: .JSON" to the request the response shows that not only were the Attributes not set but also the FriendlyName and UniqueName were nil, unlike before when they were correctly set using the URL-Encoded Parameters.
Am I setting the Attributes wrong in 'parameters'? Twilio's documentation says that Attributes is "An optional metadata field you can use to store any data you wish. No processing or validation is done on this field."
Help would be appreciated :)
I have figured out an answer to my question. Turns out I had formatted the Attributes field incorrectly.
Here is the code that worked for me:
let parameters : [String : AnyObject] = [
"FriendlyName": "foo",
"UniqueName": "bar",
"Attributes": "{\"key\":\"value\",\"foo\":\"bar\"}",
"Type": "private"
]
Alamofire.request(.POST, "https://ip-messaging.twilio.com/v1/Services/\(instanceSID)/Channels/", parameters: parameters)
.authenticate(user: user, password: password)
.responseJSON { response in
let response = String(response.result.value)
print(response)
debugPrint(response)
}
Hope this helps others :)