How to send nested raw JSON in URLSession to Vapor API? - json

I have a Vapor API that has a route to register users. This route recibes a nested object in JSON format like this:
{
"name": "Test",
"email": "test1#test.com",
"password": "Test1",
"phone": {
"numberString": "+52 999 999 9999",
"countryCode": 52,
"nationalNumber": 9999999999,
}
}
This JSON is converted into a Content/Codable Object:
final class Create: Codable, Content {
let name: String!
let email: String!
let password: String
let phone: PhoneNumber!
init(name: String, email: String, password: String, phone: PhoneNumber) {
self.name = name
self.email = email
self.password = password
self.phone = phone
}
}
I have tried this route sending the JSON string as raw via Postman and the route worked perfectly but the problem is when I try to send it via URLSession in my iOS counterpart the ErrorMiddleware throws a DecodingError:
DecodingError: Value of type 'String' required for key 'password'.
At first I thought that the problem was the JSON generation until, for test purpose I send the same JSON as in the example and the Vapor API is still throwing the Error.
let urlStr = "\(BaseURL)/api/student/register"
guard let url = URL(string: urlStr) else { return }
var urlRequest = URLRequest(url: url, cachePolicy:
.reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30.0)
let raw = "{\"name\":\"Prueba\",\"email\":\"prueba1#hotmail.com\",\"password\":\"Prueba1\",\"phone\":{\"numberString\":\"\",\"countryCode\":,\"nationalNumber\":}}"
urlRequest.httpMethod = "POST"
urlRequest.httpBody = raw.data(using: .utf8)
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
...
}.resume()
Can you even send this types of JSON's via URLSession or do I need to change my logic so it will receive a flat array?

After hours of debugging and getting strange errors I realized that my error was simpler than I thought.
The error was:
{"error":true, "reason":"Value of type 'String' required for key 'password'."}
And I tried to send in Postman a request with out the key 'password' which returned:
{"error": true, "reason": "Value required for key 'password'."}
What got me to analyze my objects and then I saw the error, my Create object wasn't unwrapped correctly, more precisely this:
let password: String
Should be having a ! next to String like this.
let password: String!
The reason why this worked on Postman and not on URLSession is still uncleared.
UPDATE:
As proposed by #vadian the headers in this URLSession are also missing and even tho after I added the force unwrapped the API passed the request but with nil content
urlRequest.setValue("application/json", forHTTPHeaderField:"Accept")
urlRequest.setValue("application/json", forHTTPHeaderField:"Content-Type")

Set also the content-type and length header
urlRequest.setValue(String(Data(json.utf8).count), forHTTPHeaderField:"Content-Length")
urlRequest.setValue("application/json", forHTTPHeaderField:"Accept")
urlRequest.setValue("application/json", forHTTPHeaderField:"Content-Type")
And never, never, never declare properties in a class as implicit unwrapped optional which are initialized with non-optional values.

Related

JSON HTTP request payload parsed in Swift is being interpreted by Express as single key in a JSON object–insight as to why?

I am working on some networking code in an iOS app with Swift and having an issue where a Codable struct is being parsed into JSON improperly, in such a way that the whole object is used as the key
My Codable struct called UserForLogin to be parsed as the body of a login request. The struct has the following code:
struct UserForLogin: Encodable, Equatable {
let email: String
let password: String
init(email: String, password: String) {
self.email = email
self.password = password
}
}
signIn looks like this:
func signIn(with data: UserForLogin) -> AnyPublisher<UserLoginDTO?, Error> {
return gcNetworkService.post(to: AuthModuleEndpoint.signin, data: data, with: [])
.tryMap { userWithToken -> UserLoginDTO in
return userWithToken
}.mapError { error in
return error
}.eraseToAnyPublisher()
}
and the .post method essentially calls another method called send which contains this code snippet:
var request = createURLRequest(for: requestURL)
request.httpMethod = method
let requestData = try? JSONEncoder().encode(data)
request.httpBody = requestData
return session.dataTaskPublisher(for: request)
When I make this call, however, the request body that my backend receives is:
{ '{"email":"test","password":"test2"}': '' }
Essentially, the whole object is being parsed as one JSON key rather than with each variable as a key and value as a value, etc. like it should.
For context, I'm successfully handling requests in the backend when sending them through Postman, so I don't think it's a server-side issue.
Any guidance would be much appreciated!
Thank you to Jacob for pointing out I was missing this line:
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
This solved the problem.

How to send json body in get request

I know sending json body in get request is violating http rules, however according to client's requirement the server is accepting json body in get request.so i want to send json body in my get request using alamofire.
i tried various parameter encoding types.nothing worked.
func listNews( withCompletionHandler:#escaping (_ status:Bool, _ message: String, _ yearLevels: JSON) -> Void){
let parameters = [
"month": "07",
"year": "2019",
"record_start": 0,
"record_offset": 100
] as [String : Any]
let headers = [
"Content-Type": "application/json"
]
let url = STAFF_SERVICE_URL+"staff/2/news?api_key=\(api_key)"
print("URL > \(url)")
Alamofire.request(url, method: .get,parameters:parameters, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
let statusCode = response.response?.statusCode
print(statusCode ?? "Success Error Code")
if(statusCode==200){
let swiftyJsonVar = JSON(response.result.value!)
print(swiftyJsonVar)
withCompletionHandler(true,"Success",swiftyJsonVar)
}else if(statusCode==400){
withCompletionHandler(false,"System Error. Please try again later.",JSON.null)
}else{
withCompletionHandler(false,"System Error",JSON.null)
}
}
}
i expect array of json objects in response. but the actual output is time out error
Actually you should change encoding method for get request. You have set encoding: JSONEncoding.default. Instead of that, use encoding: URLEncoding.default.
Second thing, if you are sending parameters in json body, then send all parameters instead of some sending with body and some in url. So, your api_key=\(api_key) should be in json body.
I hope this will fix your issue.

How do I use JSON element with alamofire

I am using two textfields to pass login information to the PHP web service using Alamofire in the following way.
#IBAction func LoginButton(_ sender: Any) {
//getting the username and password
let parameters: Parameters=[
"Name":TextFieldUserName.text!,
"Pass":TextFieldPassword.text!
]
Alamofire.request(URL_USER_LOGIN, method: .post, parameters: parameters).responseJSON
{
response in
//printing response
print(response)
The following Json data is received on login.
[{"code":0,"message":"Check Username and Password....","userid":""}]
I want to use either "code" value (0 for false and 1 for true) or "message" value as String to put into an if - else statement for further steps. If Alamofire is not the best way to go about this, can someone please show another way. Thanks in advance for the help.
Do you need to deserialize the response from the server?
The easiest option is parsing response value as NSDictionary
if let JSON = response.result.value as? NSDictionary {
let code = JSON.value(forKey: "code") as? Int
let message = JSON.value(forKey: "message") as? String
}
You can also use the Codable protocol and the JSONDecoder to decode this response into your structure.
For example, declare struct:
struct LoginResponse: Codable {
var code: Int
var message: String
}
and decode response using JSONDecoder
let jsonDecoder = JSONDecoder()
let loginResponse = try? jsonDecoder.decode(LoginResponse.self, from: response.data)

Alamofire Mailchimp API 3.0 subscribe

I am trying to subscribe new user to Mailchimp list using Alamofire.
Problem starts when I'm trying to subscribe new user with .post method and JSONObject as a parameter:
func subscribeMail(){
let credentialData = "<my_api_key>".data(using: String.Encoding.utf8)!
let base64Credentials = credentialData.base64EncodedString(options: [])
let headers = ["Authorization": "Basic \(base64Credentials)"]
let url = "https://us11.api.mailchimp.com/3.0/lists/<my_list_id>/members/"
let jsonObj: [String: AnyObject] = [
"mail_address" : "testMailAddress#gmail.com" as AnyObject,
"status" : "subscribed" as AnyObject,
]
let valid = JSONSerialization.isValidJSONObject(jsonObj)
print(valid)
Alamofire.request(url, method: .post, parameters: jsonObj , encoding: URLEncoding.default , headers: headers).responseJSON{response in
if response.result.isFailure {
print("Failed")
}
else if (response.result.value as? [String: AnyObject]) != nil {
print(response)
}
}
}
I get back status code 400:
SUCCESS: {
detail = "We encountered an unspecified JSON parsing error.";
instance = "";
status = 400;
title = "JSON Parse Error";
type = "http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/";
}
In Mailchimp documentation:
JSONParseError
We encountered an unspecified JSON parsing error.
This error means that your JSON was formatted incorrectly or was considered invalid or incomplete.
As you can see I am checking my jsonObj if its valid. So I dont get this parsing error..
In Mailchimp API 3.0 its written that just email_address and status fields are needed to subscribe new mail.
If I try to to send request with Alamofire using .get method with some mail address that is already subscribed, everything works fine, I can receive all data from Mailchimp.
Is there really problem with my jsonObj or is it somewhere else?
Change the object key from 'mail_address' to 'email_address' and give a try.
let jsonObj: [String: AnyObject] = [
"email_address" : "testMailAddress#gmail.com" as AnyObject,
"status" : "subscribed" as AnyObject,
]
Since you're getting a JSONParseError, your issue is related to the format in which you're sending the parameters.
Try encoding: JSONEncoding.default instead of encoding: URLEncoding.default.

Invalid type in JSON write (_SwiftValue) using Alamofire

I have tried a lot of things now and are still getting:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (_SwiftValue)'
UPDATE:
I scan a barcode and save the info:
let calendar = Calendar.current
let expiryDate = (calendar as NSCalendar).date(byAdding: .month, value: 3, to: Date(), options: [])?.description
let barcode = BarcodeData(barcodeValue: value,
datetime: dateTime,
expiryDate: expiryDate!,
latitude: latitude.description,
longitude: longitude.description,
status: txtStatus.text!,
type: txtType.text!,
extraText: "")
Object are then mapped to a JSON string, it seems that the slashes (/) are added by this function:
let jsonBarcode = Mapper<BarcodeData>().toJSONString(barcode)
The barcode are then added to a list of String:
barcodeDataList.append(jsonBarcode)
When I click a button I invoke the web service, that anticipate parameters in the form of:
let testParams : Parameters =
[ "udid": "my_udid",
"data": jsonArray
]
jsonArray consist of an array of the BarcodeData-object(s) as seen above.
Communication with the web service looks like:
Alamofire.request(url, method: .post, parameters: testParams, encoding: JSONEncoding.default).validate().responseJSON { response in
switch response.result {
case .success:
print("Validation successful")
if let json = response.result.value {
print("JSON: \(json)")
}
case .failure(let error):
print("Error: \(error)")
}
}
The following is passed to the ws:
["udid": "\"001-my_udid\"", "data": [
"{\"latitude\":\"0.0\",\"status\":\"Empty\",\"datetime\":\"2016-09-20 05:10\",\"longitude\":\"0.0\",\"type\":\"ABC123\",\"barcodevalue\":\"123456\"}"
]]
The json array for "data" validates at jsonlint.com and the response from the server is in the form of a json object like:
{result: "Data successfully received"}
Change your encoding in request from encoding: JSONEncoding.default to encoding: URLEncoding.default
I think you need to encode your array in json object try something like this
do{
let data = try NSJSONSerialization.dataWithJSONObject(mappingArray, options: NSJSONWritingOptions(rawValue: 0))
let mappedString : String = (String(data: data , encoding: NSUTF8StringEncoding)!)
return mappedString
}
catch{
print(error)
return error as! String
}
Append this string with your url