Cannot create Data for Multi Part Form request - swift

I am trying to send a multipart/form-data request via URLSession I have written a helper function to create the form body as Data for me, however my request keeps failing with a 500 response code.
I was trying to print out the string representation of my request to ensure the body is correct, however simply trying to decode it now is throwing an error as The data couldn’t be read because it isn’t in the correct format.
This leads me to believe something is wrong with how my request is being encoded.
This is essentially my setup -
struct Media {
let key: String
let filename: String
let data: Data
let mimeType: String
init?(withImage image: UIImage, forKey key: String) {
self.key = key
self.mimeType = "image/jpeg"
self.filename = "avatar.jpg"
guard let data = image.jpegData(compressionQuality: 0.7) else { return nil }
self.data = data
}
}
typealias Parameters = [String: String]
func createFormData(usingParams params: Parameters?, media: [Media]?, boundary: String) -> Data {
let lineBreak = "\r\n"
var body = Data()
if let parameters = params {
for (key, value) in parameters {
body.append(string:"--\(boundary + lineBreak)")
body.append(string:"Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
body.append(string:"\(value + lineBreak)")
}
}
if let media = media {
for photo in media {
body.append(string:"--\(boundary + lineBreak)")
body.append(string:"Content-Disposition: form-data; name=\"\(photo.key)\"; filename=\"\(photo.filename)\"\(lineBreak)")
body.append(string:"Content-Type: \(photo.mimeType + lineBreak + lineBreak)")
body.append(photo.data)
body.append(string: lineBreak)
}
}
body.append(string: "--\(boundary)--\(lineBreak)")
return body
}
extension Data {
mutating func append(string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
Which I invoke something like this -
let boundary = UUID().uuidString
guard let profilePic = Media(withImage:image, forKey: "file") else { return }
let data = createFormData(usingParams: nil, media: [profilePic], boundary: boundary)
I have tried to print my data using the following -
do {
print(data)
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
print(json)
} catch let error {
print(error.localizedDescription)
}
This throws the error mentioned earlier.
I have also tried
let string = String(data: data, encoding: .utf8)
print(string)
This however just returns nil.
I noticed if I remove body.append(photo.data) the data can print out now as JSON, however I am not sure why this matters

Related

How can I extract jsonString from this method that depends on a task?

var temp = ""
let appid = "**************"
struct WeatherData {
// Object with latitude and longitude to process requests
// from OpenWeatherMap.
var lat, lon: Float
init(latitude: Float, longitude: Float) {
lat = latitude
lon = longitude
}
func retrieve() {
var jsonString = ""
// Send request to OpenWeatherMap.
let requestAddress = "https://api.openweathermap.org/data/2.5/weather?lat=\(lat)&lon=\(lon)&appid=\(appid)"
// Assign the URL to retrieve JSON, with ! dangerous
// operation.
let url = URL(string: requestAddress)!
let urlSession = URLSession(configuration: .ephemeral)
let task = urlSession.dataTask(with: url) {(data, response, error) in
let data = data
jsonString = String(data: data!, encoding: .utf8)!
print(jsonString)
}
task.resume()
}
}
var bangkok = WeatherData(latitude: 13.736717, longitude: 100.523186)
print(bangkok.retrieve())
print("Program running...")
print(temp)
RunLoop.main.run()
The issue I'm having is only print() works but not a return statement or an assignment to a global variable which is what I need. I ultimately want to parse this jsonString into a working dictionary except that I can't get it out of the function at the moment.
I'm depending on a webpage that has only text as body content.
It common to return the result via a completion block:
enum AppError : String, Error
{
case unknownFailure
case requestFailed
...
}
func retrieve(completion: #escaping (Result<Data, AppError>) -> Void)
{
...
let task = urlSession.dataTask(with: url)
{ (data, response, error) in
DispatchQueue.main.async
{
if let statusCode = (response as? HTTPURLResponse)?.statusCode,
statusCode != 200
{
completion(.failure(.requestFailed))
}
else if let data = data
{
completion(.success(data))
}
else if let error = error
{
completion(.failure(.requestFailed))
}
else
{
completion(.failure(.unknownFailure))
}
}
}
task.resume()
}
You need to choose where to convert the received Data to JSON and ultimately to some Codable struct.
You must also decide on how to handle errors and what detail you want to pass to a caller. In the above example I hide the details of dataTask() errors and put them all under app-custom .requestFailed.

How to make a url post request which is returned by the function in Swift

Hi guys I am trying to contact my Rest API and get the data. I am successful in doing that but I want the function to return the string that it obtained.
This is why code so far:
private func getPost(one: String, two: String, link: String) {
let url = URL(string: link)!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let parameters: [String: Any] = [
"parent" : one,
"original": two
]
request.httpBody = parameters.percentEncoded()
var responseString = ""
print("Sarcasm \(yourMessage) \(otherMessage) \(link)")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data,
let response = response as? HTTPURLResponse,
error == nil else { // check for fundamental networking error
print("error", error ?? "Unknown error")
return
}
guard (200 ... 299) ~= response.statusCode else { // check for http errors
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}
responseString = String(data: data, encoding: .utf8)!
print("responseString = \(responseString)")
// return responseString
}
task.resume()
}
Where :
extension Dictionary {
func percentEncoded() -> Data? {
return map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
.data(using: .utf8)
}
}
extension CharacterSet {
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
}()
}
All I want is this function (getPost) to return the response string that it obtains from the post request. However, I do not know what to do. I mean the application gets the response string from the post request but then I want to modify the function so that it returns it instead of printing it.

how to send file through urlRequest from iOS(client-end)

Here is my REST API for uploading file-
#api.route('/update_profile_picture', methods=['POST'])
def update_profile_picture():
if 'file' in request.files:
image_file = request.files['file']
else:
return jsonify({'response': None, 'error' : 'NO File found in request.'})
filename = secure_filename(image_file.filename)
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
image_file.save(image_path)
try:
current_user.image = filename
db.session.commit()
except Exception as e:
return jsonify({'response': None, 'error' : str(e)})
return jsonify({'response': ['{} profile picture update successful'.format(filename)], 'error': None})
The above code works fine as I tested with postman but in postman I can set a file object.
However, when I try to upload from iOS app, it gives me the error-
NO File found in request
Here is my swift code to upload image-
struct ImageFile {
let fileName : String
let data: Data
let mimeType: String
init?(withImage image: UIImage, andFileName fileName: String) {
self.mimeType = "image/jpeg"
self.fileName = fileName
guard let data = image.jpegData(compressionQuality: 1.0) else {
return nil
}
self.data = data
}
}
class FileLoadingManager{
static let sharedInstance = FileLoadingManager()
private init(){}
let utilityClaas = Utility()
func uploadFile(atURL urlString: String, image: ImageFile, completed:#escaping(Result<NetworkResponse<String>, NetworkError>)->()){
guard let url = URL(string: urlString) else{
return completed(.failure(.invalidURL))
}
var httpBody = Data()
let boundary = self.getBoundary()
let lineBreak = "\r\n"
let contentType = "multipart/form-data; boundary = --\(boundary)"
httpBody.append("--\(boundary + lineBreak)")
httpBody.append("Content-Disposition: form-data; name = \"file\"; \(lineBreak)")
httpBody.append("Content-Type: \(image.mimeType + lineBreak + lineBreak)")
httpBody.append(image.data)
httpBody.append(lineBreak)
httpBody.append("--\(boundary)--")
let requestManager = NetworkRequest(withURL: url, httpBody: httpBody, contentType: contentType, andMethod: "POST")
let urlRequest = requestManager.urlRequest()
let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error as? NetworkError{
completed(.failure(error))
return
}
if let response = response as? HTTPURLResponse{
if response.statusCode < 200 || response.statusCode > 299{
completed(.failure(self.utilityClaas.getNetworkError(from: response)))
return
}
}
guard let responseData = data else{
completed(.failure(NetworkError.invalidData))
return
}
do{
let jsonResponse = try JSONDecoder().decode(NetworkResponse<String>.self, from: responseData)
completed(.success(jsonResponse))
}catch{
completed(.failure(NetworkError.decodingFailed))
}
}
dataTask.resume()
}
private func boundary()->String{
return "--\(NSUUID().uuidString)"
}
}
extension Data{
mutating func append(_ string: String) {
if let data = string.data(using: .utf8){
self.append(data)
}
}
}
Also here is the NetworkRequest struct-
class NetworkRequest{
var url: URL
var httpBody: Data?
var httpMethod: String
var contentType = "application/json"
init(withURL url:URL, httpBody body:Data, contentType type:String?, andMethod method:String) {
self.url = url
self.httpBody = body
self.httpMethod = method
if let contentType = type{
self.contentType = contentType
}
}
func urlRequest()->URLRequest{
var request = URLRequest(url: self.url)
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
request.httpBody = self.httpBody
request.httpMethod = self.httpMethod
return request
}
}
In The ImageLoaderViewController, an image is selected to be sent to be uploaded.
class ImageLoaderViewController: UIViewController {
#IBOutlet weak var selectedImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func selectImage(){
if selectedImageView.image != nil{
selectedImageView.image = nil
}
let imagePicker = UIImagePickerController()
imagePicker.sourceType = .photoLibrary
imagePicker.delegate = self
self.present(imagePicker, animated: true, completion: nil)
}
#IBAction func uploadImageToServer(){
if let image = imageFile{
DataProvider.sharedInstance.uploadPicture(image) { (msg, error) in
if let error = error{
print(error)
}
else{
print(msg!)
}
}
}
}
func completedWithImage(_ image: UIImage) -> Void {
imageFile = ImageFile(withImage: image, andFileName: "test")
}
}
extension ImageLoaderViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage{
picker.dismiss(animated: true) {
self.selectedImageView.image = image
self.completedWithImage(image)
}
}
picker.dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
The mistake is that you call boundary() function each time in your code that generates you new UUID but the resource must have a single one. So just generate UUID for your resource once and then insert this value where you need:
...
let boundary = boundary()
let contentType = "multipart/form-data; boundary = \(boundary)"
...
Setting up a multipart form-data content can be tricky. Especially there can be subtle errors when combining the many parts of the request body.
Content-Type request header value:
let contentType = "multipart/form-data; boundary = --\(boundary)"
Here, the boundary parameter should not be preceded with the prefix "--".
Also, remove any WS that are not explicitly allowed according the corresponding RFC.
Furthermore, enclosing the boundary parameter in double quotes makes it more robust and never hurts:
let contentType = "multipart/form-data; boundary=\"\(boundary)\""
Initial body:
httpBody.append("--\(boundary + lineBreak)")
This is the start of the body. Before the body, the request headers are written into the body stream. Each header is completed with a CRLF, and after the last header another CRLF must be written. Well, I am pretty sure, URLRequest will ensure this. Nonetheless, it might be worth checking this with a tool that shows the characters written over the wire.
Otherwise, add a preceding CRLF to the boundary (which conceptually belongs to the boundary anyway, and it does not hurt also):
httpBody.append("\(lineBreak)--\(boundary)\(lineBreak)")
Content-Disposition:
httpBody.append("Content-Disposition: form-data; name = \"file\"; \(lineBreak)")
Here, again you may remove the additional WS:
httpBody.append("Content-Disposition: form-data; name=\"file\"; \(lineBreak)")
Optionally, you may want to provide a filename parameter and a value. This is not mandatory, though.
Closing boundary
There's no error here:
httpBody.append(lineBreak)
httpBody.append("--\(boundary)--")
But you might want to make it clear, that the preceding CRLF belongs to the boundary:
httpBody.append("\(lineBreak)--\(boundary)--")
Characters after the closing boundary will be ignored by the server.
Encoding
extension Data{
mutating func append(_ string: String) {
if let data = string.data(using: .utf8){
self.append(data)
}
}
}
You cannot generally return utf8 encoded strings and embed this into the many different parts of a HTTP request body. Many parts of the HTTP protocol allow only a restricted set of characters. In many cases, UTF-8 is not allowed. You have to look-up the details in the corresponding RFCs - which is cumbersome, but also enlightened ;)
References:
RFC 7578, Definition of multipart/form-data
This is my way to upload a file from IOS Client using multipart form , with the help of Alamofire library
let url = "url here"
let headers: HTTPHeaders = [
"Authorization": "Bearer Token Here",
"Accept": "application/x-www-form-urlencoded"
]
AF.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(imageData, withName: "image" ,fileName: "image.png" , mimeType: "image/png")
}, to: url, method: .post ,headers: headers).validate(statusCode: 200..<300).response { }
Here is nice example of Multipart. I think it could be something wrong with building multipart:
let body = NSMutableData()
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
if fileURLs != nil {
if fileKeyName == nil {
throw NSError(domain: NSBundle.mainBundle().bundleIdentifier ?? "NSURLSession+Multipart", code: -1, userInfo: [NSLocalizedDescriptionKey: "If fileURLs supplied, fileKeyName must not be nil"])
}
for fileURL in fileURLs! {
let filename = fileURL.lastPathComponent
guard let data = NSData(contentsOfURL: fileURL) else {
throw NSError(domain: NSBundle.mainBundle().bundleIdentifier ?? "NSURLSession+Multipart", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unable to open \(fileURL.path)"])
}
let mimetype = NSURLSession.mimeTypeForPath(fileURL.path!)
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(fileKeyName!)\"; filename=\"\(filename!)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.appendData(data)
body.appendString("\r\n")
}
}
body.appendString("--\(boundary)--\r\n")
return body

How to convert a base64String to String in Swift?

I am receiving a base64String from webservice response in NSData, how to convert that base64String to String in swift?
//Code
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as! NSDictionary // Response JSON from webservice
var base64String : String = ""
base64String = jsonResult["Base64String"] as! String // Retrieve base64String as string from json response
println("Base64String Alone: \(base64String)")
// Code to decode that base64String
let decodedData = NSData(base64EncodedString: base64String, options: NSDataBase64DecodingOptions(rawValue: 0))
println("Decoded: \(decodedData)")
let decodedString = NSString(data: decodedData!, encoding: NSUTF8StringEncoding)
println(decodedString) // Prints nil
Encode and decode of base64String works well for the files containing only text, if a file contains some table formats/images both encoding and decoding gives an invalid base64String. How to convert a file into base64String encode and decode whatever the contents of file ?
File formats are doc, docx, pdf, txt
Advance thanks for any help !
Try this:
let base64Encoded = "YW55IGNhcm5hbCBwbGVhc3VyZS4="
var decodedString = ""
if let decodedData = Data(base64Encoded: base64Encoded) {
decodedString = String(data: decodedData, encoding: .utf8)!
}
if !decodedString.isEmpty {
print(decodedString)
} else {
print("Oops, invalid input format!")
}
Make sure your base 64 encoded string is valid.
WARNING (edit)
In most base64 decoding implementations like Java, the padding-character is not needed, but Data(base64Encoded:) returns nil if it's missing.
Swift 5 solution; use String.fromBase64(_:) instead, after implementing like:
extension Data {
/// Same as ``Data(base64Encoded:)``, but adds padding automatically
/// (if missing, instead of returning `nil`).
public static func fromBase64(_ encoded: String) -> Data? {
// Prefixes padding-character(s) (if needed).
var encoded = encoded;
let remainder = encoded.count % 4
if remainder > 0 {
encoded = encoded.padding(
toLength: encoded.count + 4 - remainder,
withPad: "=", startingAt: 0);
}
// Finally, decode.
return Data(base64Encoded: encoded);
}
}
extension String {
public static func fromBase64(_ encoded: String) -> String? {
if let data = Data.fromBase64(encoded) {
return String(data: data, encoding: .utf8)
}
return nil;
}
}
As mentioned on editor's profile,
above edit's code allows Apache 2.0 license as well,
without attribution need.
Swift extension is handy.
extension String {
func base64Encoded() -> String? {
return data(using: .utf8)?.base64EncodedString()
}
func base64Decoded() -> String? {
guard let data = Data(base64Encoded: self) else { return nil }
return String(data: data, encoding: .utf8)
}
}
"heroes".base64Encoded() // It will return: aGVyb2Vz
"aGVyb2Vz".base64Decoded() // It will return: heroes
i've made an update to Ashok Kumar S answer to add filler character when string size is not divisible by 4, raising an exception and returning nil
extension String {
func base64Encoded() -> String? {
return data(using: .utf8)?.base64EncodedString()
}
func base64Decoded() -> String? {
var st = self;
if (self.count % 4 <= 2){
st += String(repeating: "=", count: (self.count % 4))
}
guard let data = Data(base64Encoded: st) else { return nil }
return String(data: data, encoding: .utf8)
}
You can encrypt/decrypt base64 strings using this extension:
public extension String {
var base64Decoded: String? {
guard let decodedData = Data(base64Encoded: self) else { return nil }
return String(data: decodedData, encoding: .utf8)
}
var base64Encoded: String? {
let plainData = data(using: .utf8)
return plainData?.base64EncodedString()
}
}
To Encode:
"Hello World!".base64Encoded
Result is an Optional string: "SGVsbG8gV29ybGQh"
To Decode:
"SGVsbG8gV29ybGQh".base64Decoded
Result is an Optional string: "Hello World!"
Source
The above answers are core, but i had an error like
fatal error, found nil while unwrapping an optional value
The solution is adding options
extension String {
//: ### Base64 encoding a string
func base64Encoded() -> String? {
if let data = self.data(using: .utf8) {
return data.base64EncodedString()
}
return nil
}
//: ### Base64 decoding a string
func base64Decoded() -> String? {
if let data = Data(base64Encoded: self, options: .ignoreUnknownCharacters) {
return String(data: data, encoding: .utf8)
}
return nil
}
}
and use it safely
var str = "HelloWorld"
if let base64Str = str.base64Encoded() {
print("Base64 encoded string: \"\(base64Str)\"")
if let trs = base64Str.base64Decoded() {
print("Base64 decoded string: \"\(trs)\"")
}
}

Sending json array via Alamofire

I wonder if it's possible to directly send an array (not wrapped in a dictionary) in a POST request. Apparently the parameters parameter should get a map of: [String: AnyObject]?
But I want to be able to send the following example json:
[
"06786984572365",
"06644857247565",
"06649998782227"
]
You can just encode the JSON with NSJSONSerialization and then build the NSURLRequest yourself. For example, in Swift 3:
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let values = ["06786984572365", "06644857247565", "06649998782227"]
request.httpBody = try! JSONSerialization.data(withJSONObject: values)
AF.request(request) // Or `Alamofire.request(request)` in prior versions of Alamofire
.responseJSON { response in
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)
}
}
For Swift 2, see previous revision of this answer.
For swift 3 and Alamofire 4 I use the following ParametersEncoding and Array extension:
import Foundation
import Alamofire
private let arrayParametersKey = "arrayParametersKey"
/// Extenstion that allows an array be sent as a request parameters
extension Array {
/// Convert the receiver array to a `Parameters` object.
func asParameters() -> Parameters {
return [arrayParametersKey: self]
}
}
/// Convert the parameters into a json array, and it is added as the request body.
/// The array must be sent as parameters using its `asParameters` method.
public struct ArrayEncoding: ParameterEncoding {
/// The options for writing the parameters as JSON data.
public let options: JSONSerialization.WritingOptions
/// Creates a new instance of the encoding using the given options
///
/// - parameter options: The options used to encode the json. Default is `[]`
///
/// - returns: The new instance
public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters,
let array = parameters[arrayParametersKey] else {
return urlRequest
}
do {
let data = try JSONSerialization.data(withJSONObject: array, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
Basically, it converts the array to a Dictionary in order to be accepted as Parameters argument, and then it takes back the array from the dictionary, convert it to JSON Data and adds it as the request body.
Once you have it, you can create request this way:
let values = ["06786984572365", "06644857247565", "06649998782227"]
Alamofire.request(url,
method: .post,
parameters: values.asParameters(),
encoding: ArrayEncoding())
Here is an example of encoding an Array of type Thing to JSON, using a router, and Ogra to do the JSON encoding:
import Foundation
import Alamofire
import Orga
class Thing {
...
}
enum Router: URLRequestConvertible {
static let baseURLString = "http://www.example.com"
case UploadThings([Thing])
private var method: Alamofire.Method {
switch self {
case .UploadThings:
return .POST
}
}
private var path: String {
switch self {
case .UploadThings:
return "upload/things"
}
}
var URLRequest: NSMutableURLRequest {
let r = NSMutableURLRequest(URL: NSURL(string: Router.baseURLString)!.URLByAppendingPathComponent(path))
r.HTTPMethod = method.rawValue
switch self {
case .UploadThings(let things):
let custom: (URLRequestConvertible, [String:AnyObject]?) -> (NSMutableURLRequest, NSError?) = {
(convertible, parameters) in
var mutableRequest = convertible.URLRequest.copy() as! NSMutableURLRequest
do {
let jsonObject = things.encode().JSONObject()
let data = try NSJSONSerialization.dataWithJSONObject(jsonObject, options: NSJSONWritingOptions.PrettyPrinted)
mutableRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
mutableRequest.HTTPBody = data
return (mutableRequest, nil)
} catch let error as NSError {
return (mutableRequest, error)
}
}
return ParameterEncoding.Custom(custom).encode(r, parameters: nil).0
default:
return r
}
}
}
Swift 2.0
This code below post object array.This code is tested on swift 2.0
func POST(RequestURL: String,postData:[AnyObject]?,successHandler: (String) -> (),failureHandler: (String) -> ()) -> () {
print("POST : \(RequestURL)")
let request = NSMutableURLRequest(URL: NSURL(string:RequestURL)!)
request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var error: NSError?
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(postData!, options:[])
} catch {
print("JSON serialization failed: \(error)")
}
Alamofire.request(request)
.responseString{ response in
switch response.result {
case .Success:
print(response.response?.statusCode)
print(response.description)
if response.response?.statusCode == 200 {
successHandler(response.result.value!)
}else{
failureHandler("\(response.description)")
}
case .Failure(let error):
failureHandler("\(error)")
}
}
}
#manueGE 's answer is right. I have a similar approach according to alamofire github's instruction:
`
struct JSONDocumentArrayEncoding: ParameterEncoding {
private let array: [Any]
init(array:[Any]) {
self.array = array
}
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = urlRequest.urlRequest
let data = try JSONSerialization.data(withJSONObject: array, options: [])
if urlRequest!.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest!.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest!.httpBody = data
return urlRequest!
}
}
`
Then call this by customize a request instead of using the default one with parameter. Basically discard the parameter, since it is a dictionary.
let headers = getHeaders()
var urlRequest = URLRequest(url: URL(string: (ServerURL + Api))!)
urlRequest.httpMethod = "post"
urlRequest.allHTTPHeaderFields = headers
let jsonArrayencoding = JSONDocumentArrayEncoding(array: documents)
let jsonAryEncodedRequest = try? jsonArrayencoding.encode(urlRequest, with: nil)
request = customAlamofireManager.request(jsonAryEncodedRequest!)
request?.validate{request, response, data in
return .success
}
.responseJSON { /*[unowned self] */(response) -> Void in
...
}
Also, the way to handle error in data is very helpful.
let url = try Router.baseURL.asURL()
// Make Request
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
urlRequest.httpMethod = "post"
// let dictArray: [[String: Any]] = []
urlRequest = try! JSONEncoding.default.encode(urlRequest, withJSONObject: dictArray)
Something I do in my project to upload a JSON array
func placeOrderApi(getUserId:String,getDateId:String,getTimeID:String,getAddressId:String,getCoupon:String)
{
let data = try! JSONSerialization.data(withJSONObject: self.arrOfServices, options: [])
let jsonBatch : String = String(data: data, encoding: .utf8)!
//try! JSONSerialization.data(withJSONObject: values)
let params = [
"user_id":getUserId,
"time_id":getTimeID,
"date_id":getDateId,
"address_id":getAddressId,
"services":jsonBatch,
"payment_mode":paymentVia,
"coupon":getCoupon
] as [String : Any]
print(params)
self.objHudShow()
Alamofire.request(BaseViewController.API_URL + "place_order", method: .post, parameters: params, encoding: JSONEncoding.default)
.responseJSON { response in
debugPrint(response)
switch response.result {
case .success (let data):
print(data)
self.objHudHide()
if response.result.value != nil
{
let json : JSON = JSON(response.result.value!)
if json["status"] == true
{
}
else
{
self.view.makeToast(NSLocalizedString(json["msg"].string ?? "", comment: ""), duration: 3.0, position: .bottom)
}
}
break
case .failure:
self.objHudHide()
print("Error in upload:)")
break
}
}
}
There are 2 approach to send send JSON content as parameter.
You can send json as string and your web service will parse it on server.
d["completionDetail"] = "[{"YearOfCompletion":"14/03/2017","Completed":true}]"
You can pass each value within your json (YearOfCompletion and Completed) in form of sequential array. And your web service will insert that data in same sequence. Syntax for this will look a like
d["YearOfCompletion[0]"] = "1998"
d["YearOfCompletion[1]"] = "1997"
d["YearOfCompletion[2]"] = "1996"
d["Completed[0]"] = "true"
d["Completed[1]"] = "false"
d["Completed[2]"] = "true"
I have been using following web service call function with dictionary, to trigger Alamofire request Swift3.0.
func wsDataRequest(url:String, parameters:Dictionary<String, Any>) {
debugPrint("Request:", url, parameters as NSDictionary, separator: "\n")
//check for internete collection, if not availabale, don;t move forword
if Rechability.connectedToNetwork() == false {SVProgressHUD.showError(withStatus: NSLocalizedString("No Network available! Please check your connection and try again later.", comment: "")); return}
//
self.request = Alamofire.request(url, method: .post, parameters: parameters)
if let request = self.request as? DataRequest {
request.responseString { response in
var serializedData : Any? = nil
var message = NSLocalizedString("Success!", comment: "")//MUST BE CHANGED TO RELEVANT RESPONSES
//check content availability and produce serializable response
if response.result.isSuccess == true {
do {
serializedData = try JSONSerialization.jsonObject(with: response.data!, options: JSONSerialization.ReadingOptions.allowFragments)
//print(serializedData as! NSDictionary)
//debugPrint(message, "Response Dictionary:", serializedData ?? "Data could not be serialized", separator: "\n")
}catch{
message = NSLocalizedString("Webservice Response error!", comment: "")
var string = String.init(data: response.data!, encoding: .utf8) as String!
//TO check when html coms as prefix of JSON, this is hack mush be fixed on web end.
do {
if let index = string?.characters.index(of: "{") {
if let s = string?.substring(from: index) {
if let data = s.data(using: String.Encoding.utf8) {
serializedData = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
debugPrint(message, "Courtesy SUME:", serializedData ?? "Data could not be serialized", separator: "\n")
}
}
}
}catch{debugPrint(message, error.localizedDescription, "Respone String:", string ?? "No respone value.", separator: "\n")}
//let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)
debugPrint(message, error.localizedDescription, "Respone String:", string ?? "No respone value.", separator: "\n")
}
//call finised response in all cases
self.delegate?.finished(succes: response.result.isSuccess, and: serializedData, message: message)
}else{
if self.retryCounter < 1 {//this happens really frequntly so in that case this fn being called again as a retry
self.wsDataRequest(url: url, parameters: parameters)
}else{
message = response.error?.localizedDescription ?? (NSLocalizedString("No network", comment: "")+"!")
SVProgressHUD.showError(withStatus: message);//this will show errror and hide Hud
debugPrint(message)
//call finised response in all cases
self.delay(2.0, closure: {self.delegate?.finished(succes: response.result.isSuccess, and: serializedData, message:message)})
}
self.retryCounter += 1
}
}
}
}
I think based on Alamofire documentation you can write the code as following:
let values = ["06786984572365", "06644857247565", "06649998782227"]
Alamofire.request(.POST, url, parameters: values, encoding:.JSON)
.authenticate(user: userid, password: password)
.responseJSON { (request, response, responseObject, error) in
// do whatever you want here
if responseObject == nil {
println(error)
} else {
println(responseObject)
}
}