I am trying to upload (post) a JSON string using Alamofire and I'm getting an error that leads me to believe there is some issue in the dictionary encoding.
The array I am encoding is an array of objects, all of type String. It looks like this:
class SavedDeliveries: Codable { //not sure if this is the issue (I use this to store default values locally)
var fullName = ""
var address = ""
var city = ""
var zip = ""
var phone = ""
var orders = ""
var ordersListed = ""
var pickup = ""
}
The code including the Alamofire call looks like this:
func postData() {
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"X-Master-Key": "xxx", //API Key
"X-Bin-Name": "deliverydata"]
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(deliveryList)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
print(json!) // printing the JSON and it is correct when validated.
AF.request("https://api.jsonbin.io/v3/b", method: .post, parameters: json, encoding: JSONEncoding.default, headers: headers).responseString { (response) in
switch response.result {
case .success:
print("was successful")
case let .failure(error):
print(error)
}
}
}
I expect it to upload the JSON file but I'm getting an error message that says this:
Cannot convert value of type 'String?' to expected argument type 'Parameters?' (aka 'Optional<Dictionary<String, Any>>')
Not sure if the AF call parameter is expecting a certain kind of dictionary key:value format. If this is not the right call format for uploading JSON, what do I need to change?
Thanks for any help. I'm not a full-time Swift developer but manage an app that is usually within my capabilities. This one has me stumped.
I guess I don't understand much about HTTP requests or Alamofire, but I was able to solve this issue with the following mods to my code (without Alamofire, which seems like overkill in this case).
func postData() {
// Prepare URL
let url = URL(string: "https://api.jsonbin.io/v3/b")
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
// Set HTTP Request Body
let header: HTTPHeaders = [
"Content-Type": "application/json",
"X-Master-Key": "xxx",
"X-Bin-Name": "deliverydata"
]
request.headers = header
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(deliveryList)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
request.httpBody = json!.data(using: String.Encoding.utf8)
// Perform HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check for Error
if let error = error {
print("Error took place \(error)")
return
}
// Convert HTTP Response Data to a String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Response data string:\n \(dataString)")
}
}
task.resume()
}
To make such a request using Alamofire, I'd recommend:
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"X-Master-Key": "xxx", //API Key
"X-Bin-Name": "deliverydata"]
AF.request("https://api.jsonbin.io/v3/b",
method: .post,
parameters: deliveryList,
headers: headers).responseString { response in
switch response.result {
case .success:
print("was successful")
case let .failure(error):
print(error)
}
}
Even better, create a Decodable response type and use responseDecodable to parse it.
I would also suggest not using empty strings as your default values, that can easily lead to sending bad or invalid data to the backend.
Related
I have a problem sending a POST request with Vapor 3 with the body including JSON. I am using https://docs.postman-echo.com/ to test this, where it responds back with the same JSON it is sent.
I have viewed answers on here, but got errors with encoding and content types.
router.get("hooray") { req -> Future<View> in
var postHeaders: HTTPHeaders = .init()
postHeaders.add(name: .contentType, value: "application/json")
postHeaders.add(name: .accept, value: "*/*")
postHeaders.add(name: .acceptEncoding, value: "gzip, deflate")
let oneField = singleGet(foo: "barfoobar")
// { foo: "barfoobar" } - JSON string
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try encoder.encode(oneField)
let jsonString = String(data: jsonData, encoding: .utf8)!
let postBody = HTTPBody(string: jsonString)
let httpReq = HTTPRequest(method: .POST, url: "/post")
let httpRes = HTTPClient.connect(hostname: "postman-echo.com", on: req)
.flatMap(to: singleGet.self) { client in
req.http.headers = postHeaders
req.http.contentType = .json
req.http.body = postBody
return client.send(httpReq).flatMap(to: singleGet.self) { res in
return try req.content.decode(singleGet.self)
}
}
return try req.view().render("test", httpRes)
}
struct singleGet: Content {
let foo: String
}
I am getting the correct response with this code, but I was wondering when I rework the code to match this answer, why do I get errors?
I've added the headers and body to the request and comment them out inside the closure, but it won't work this way.
let httpReq = HTTPRequest(method: .POST, url: "/post", headers: postHeaders, body: postBody)
I want to send multiple different JSON object in a single request, i felt streaming the multiple JSON objects like a file in a single request would be better, so kindly let me know if it is possible, if so kindly give me an idea of how to do it using Alamofire, Below is the formate of the raw body (application/json) data that i want to post
{"a":23214,"b":54674,"c":"aa12234","d":4634}
{"a":32214,"b":324674,"c":"as344234","d":23434}
{"a":567214,"b":89674,"c":"sdf54234","d"34634}
I tried the below code, but it didn't work as the body param is not in the correct format, that's the reason i want to try sending the multiple JSON objects as a stream in a single request, Kindly advice
let SendParams = [
["a":1234, "b":2345, "c":3456], ["a":2345, "b":3456, "c":4567], ["a":3456, "b":4567, "c":5678]
]
_ = Alamofire.request(url, method: .post, parameters: params, encoding: JSONEncoding, headers: header)
JSON format is not correct for your request, JSON is always key-value pair, where key is always String, and value is any Object. In your example need to set key at top level for object of type Array as below:
let SendParams = [
"key" :[["a":1234, "b":2345, "c":3456], ["a":2345, "b":3456, "c":4567], ["a":3456, "b":4567, "c":5678]]
]
_ = Alamofire.request(url, method: .post, parameters: SendParams, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in}
OR
Serialise the array and set as httpBody of URLRequest object:
let url = YOUR_POST_API_URL
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let values = [
["a":1234, "b":2345, "c":3456], ["a":2345, "b":3456, "c":4567], ["a":3456, "b":4567, "c":5678]
]
request.httpBody = try! JSONSerialization.data(withJSONObject: values)
Alamofire.request(request)
.responseJSON { response in
// do whatever you want here
switch response.result {
case .failure(let error):
print(error)
if let data = response.data, let responseString = String(data: data, encoding: .utf8) {
print(responseString)
}
case .success(let responseObject):
print(responseObject)
}
}
I am using the JSONEncoder that has been provided with Swift4.
I have a class called Customer that uses the Codable protocol. Inside of Customer there are four Strings.
class Customer: Codable {
var title: String
var firstName: String
var lastName: String
var email: String
}
Reading JSON with a GET Request works fine.
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
do {
let customer = try JSONDecoder().decode(Customer.self, from: data)
DispatchQueue.main.async {
print("\(customer.title)")
print("\(customer.firstName)")
print("\(customer.lastName)")
print("\(customer.email)")
}
} catch let jsonError {
print(jsonError)
}
}.resume()
However when I start to do a POST request I am lost:
First set up the request object:
let urlString = "http://localhost:8000/customer"
guard let url = URL(string: urlString) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
let customer = Customer()
customer.title = "Mr"
customer.firstName = "Chuck"
customer.lastName = "Norris"
customer.email = "chuck.norris#awsome.com"
let encodedData = try? JSONEncoder().encode(customer)
print(String(data: encodedData!, encoding: .utf8)!) //<- Looks as intended
// Output is {"firstName":"Chuck","lastName":"Norris","title":"MR","email":"chuck.norris#awesome.com "}
Now send it out
request.httpBody = encodedData //
URLSession.shared.dataTask(with: request, completionHandler: {(data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.data(withJSONObject: data, options: [])
print(json)
}catch {
print(error)
}
}
})
In return I receive the message "The given data was not valid JSON."
So, my assumption is that I simply can not just put the encoded JSON data into the http body of my request.
Browsing through some articles about URLSession and JSON I found that it seems that I need to serialize my encoded JSON:
var json: Any?
if let data = encodedData {
json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
}
Now, I don't know how to proceed. First I do not understand why I should serialize my JSON to something that can not simply put into a httpBody.
Because JSONSerialization.jsonObject produces Any? and not Data.
Update
Now, I was able to successfully send my data to my server. I am still trying to understand what was wrong - because I did not changed anything (except for the removal of the JSONSerialization call inside the completion Handler. I will investigate further...
...And .resume had been missing. :-)
I am new to Swift. Trying to make a post request to laravel on localhost. To verify my Request recieved from swift within laravel. I am returning Request as JSON response. which produces this error.
Code 3840 "JSON text did not start with array or object and option to
allow fragments not set."
Which means a malformed JSON response.
Laravel UserController
public function verify_login(Request $request)
{
return response()->json($request)
}
ViewController.swift
#IBAction func verify_login(_ sender: UIButton) {
let username: String = self.username.text!
let passkey: String = self.passkey.text!
//print(username)
let urlString = "http://localhost:8888/user/verify"
guard let requestUrl = URL(string:urlString) else { return }
let parameters = "username=\(username)&passkey=\(passkey)"
var request = URLRequest(url:requestUrl)
request.httpMethod = "POST"
request.setValue("application/x-www-form-unlencoded;charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = parameters.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
if error == nil,let usableData = data {
var json: [String: Any]?
do {
json = try JSONSerialization.jsonObject(with: usableData) as? [String:Any]
print(json?["username"]! as Any)
}catch{
print(error)
}
}
}
task.resume()
}
NOTE:
Using Postman I recieve the expected response (Request object as json).
Swift end code works fine with JSON Placeholder API
Hi i'm trying to replicate the following curl command in swift using NSURLSession in a playground.
curl -k -i -H "Accept: application/json" -H "X-Application: " -X POST -d 'username=&password=' https://address.com/api/login
Here's what I've got so far. Where i'm struggling is that i'm unsure how to send the post data e.g.: 'username=&password='. Any help would be appreciated.
Thanks in advance.
import Foundation
import XCPlayground
// Let asynchronous code run
XCPSetExecutionShouldContinueIndefinitely()
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.HTTPAdditionalHeaders = ["Accept" : "application/json", "X-Application" : "<AppKey>"]
let session = NSURLSession(configuration: config)
var running = false
let url = NSURL(string: "https://address.com/api/login")
let task = session.dataTaskWithURL(url!) {
(let data, let response, let error) in
if let httpResponse = response as? NSHTTPURLResponse {
let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
println(dataString)
}
running = false
}
running = true
task.resume()
You can create a mutable URLRequest and set the httpBody. But you should also percent escape the values for username and, more importantly, for password.
So, imagine your request being created like so:
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = ["Accept" : "application/json", "X-Application" : "<AppKey>", "Content-Type" : "application/x-www-form-urlencoded"]
let session = URLSession(configuration: config)
let url = URL(string: "https://identitysso.betfair.com/api/login")!
var request = URLRequest(url: url)
request.setBodyContent(["username": username, "password": password])
let task = session.dataTask(with: request) { data, response, error in
// make sure there wasn't a fundamental networking error
guard let data = data, let response = response as? HTTPURLResponse, error == nil else {
print(error ?? "Unknown error")
return
}
// if you're going to check for NSHTTPURLResponse, then do something useful
// with it, e.g. see if server status code indicates that everything is OK
guard 200 ..< 300 ~= response.statusCode else {
print("statusCode not 2xx; was \(response.statusCode)")
return
}
// since you set `Accept` to JSON, I'd assume you'd want to parse it;
// In Swift 4 and later, use JSONDecoder; in Swift 3 use JSONSerialization
do {
if let responseObject = try JSONSerialization.jsonObject(with: data) as? [String: AnyObject] {
print(responseObject)
}
} catch let parseError {
print(parseError)
print(String(data: data, encoding: .utf8) ?? data as NSData)
}
}
task.resume()
So the question is how setBodyContent builds the request body given a dictionary. Yes, you want to percent-escape anything not in the unreserved character set, but sadly CharacterSet.urlQueryAllowed is not up to the job. So you might do something like:
extension URLRequest {
/// Populate the HTTPBody of `application/x-www-form-urlencoded` request
///
/// - parameter parameters: A dictionary of keys and values to be added to the request
mutating func setBodyContent(_ parameters: [String : String]) {
let parameterArray = parameters.map { (key, value) -> String in
let encodedKey = key.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
let encodedValue = value.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
return "\(encodedKey)=\(encodedValue)"
}
httpBody = parameterArray
.joined(separator: "&")
.data(using: .utf8)
}
}
extension CharacterSet {
/// Character set containing characters allowed in query value as outlined in RFC 3986.
///
/// RFC 3986 states that the following characters are "reserved" characters.
///
/// - General Delimiters: ":", "#", "[", "]", "#", "?", "/"
/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
///
/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
/// should be percent-escaped in the query string.
///
/// - parameter string: The string to be percent-escaped.
///
/// - returns: The percent-escaped string.
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode)
return allowed
}()
}
Furthermore, I generally use a more complicated setBodyContent that also accepts numeric, boolean, and date types, but I didn't want to digress too far from your core question, how to properly build request for two string key/values pairs.
For Swift 2 rendition, see previous revision of this answer.
maybe this help, i use it to send post data:
var paramString = ""
var request:NSMutableURLRequest = NSMutableURLRequest(URL:url)
request.HTTPMethod = "POST"
var user = "MyUsername".stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
var pass = "MyPassword".stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
paramString = "username="+user!+"&password="+pass!
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
let conf: NSURLSessionConfiguration = NSURLSession.sharedSession().configuration
let session = NSURLSession(configuration: conf)
session.dataTaskWithRequest(request) {
data, response, error in
let resp = NSString(data: data, encoding: NSUTF8StringEncoding)
}.resume()
NSUTF8StringEncoding can be replaced with whatever you need