I am trying to make an http get request and I want to go back to convert the data obtained in JSON to an Array and send it as a parameter to a leaf template from the routes.swift file, all this using Vapor framework but I get the following error:
Error: missing argument for parameter 'from' in call
let json = JSONDecoder().decode(Todos.self).
Here is my code:
app.get("datos") {req -> EventLoopFuture<View> in
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")
guard let requestUrl = url else { fatalError("Error url") }
var request = URLRequest(url: requestUrl)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Error took place \(error.localizedDescription)")
return
}
if let response = response as? HTTPURLResponse {
print("Response HTTP Status code: \(response.statusCode)")
}
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Response data string:\n \(dataString)")
}
}
task.resume()
let json = JSONDecoder().decode(Todos.self)
return req.view.render("objetos", json)
}
struct Todos: Codable{
var userId: Int
var id: Int
var title: String
var body: String
}
Don't use URLSession - it doesn't integrate nicely with Vapor's futures and Vapor's client built on top of AsyncHTTPClient is much more performant and integrated. You can then rewrite your code to look like:
app.get("datos") { req -> EventLoopFuture<View> in
req.client.get("https://jsonplaceholder.typicode.com/posts").flatMap { response in
let todos = try response.content.decode(Todos.self)
return req.view.render("objetos", json)
}
}
Currently I'm trying to program an https post request for the Plant Net API (https://my.plantnet.org/account/doc#) in Swift. I want to have one to five images inside my https post request and each image has as parameter organ (in my case this is always "leaf"). When I try to run my application, I get the error code 400 "Invalid multipart payload format". I'm not sure how I can solve this problem.
//
// PlantRecognition.swift
// Happy Plant
//
// Insert several images of the same plant (maximum of 5) and get the result list
//
import Foundation
import UIKit
class PlantRecognition {
//List with images of the plant to be recognized
var imageList: [UIImage] = []
//Api key for the plant net access
var API_KEY: String = "MY_API_KEY"
var plantNetURL: String = ""
init() {
//Set plant net url after class initialization
plantNetURL = "https://my-api.plantnet.org/v2/identify/all?api-key{\(API_KEY)}"
}
func recognizeImages(images: [UIImage]) {
//Try to create the api endpoint url
guard let apiEndpoint = URL(string: "https://my-api.plantnet.org/v2/identify/all?api-key=\(API_KEY)") else {print("Error creating api endpoint"); return }
//Convert the UIImages to jpeg
let imagesJPG = convertImagesToJPEG(images: images)
//Create url request
var request = URLRequest(url: apiEndpoint)
//Set the http method to post (local images)
request.httpMethod = "POST"
//Create boundary which marks the start/end of the request body
let boundary = "Boundary-\(UUID().uuidString)"
//Set value for our specific http header field
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
//Set the mime type to jpg (we will convert our UIImages to jpg)
let mimeType = "image/jpg"
//Create http body
var body = Data()
let boundaryPrefix = " — \(boundary)\r\n"
//Add the parameter to the body (Currently just leaf recognition is possible)
body.append("--\(boundaryPrefix)\r\n")
body.append("Content-Disposition: form-data; name=\"organs\r\n\r\n")
body.append("leaf\r\n")
//Add the images to the body
var filename = String()
for (index,data) in imagesJPG.enumerated(){
filename = "image\(index)"
body.append(boundaryPrefix)
body.append("Content-Disposition: form-data; name=\"image[]\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimeType)\r\n\r\n")
body.append(data)
body.append("\r\n")
}
body.append("--\(boundary)--\r\n")
//Post the request
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
}
func convertImagesToJPEG(images: [UIImage]) -> [Data] {
var dataArray = [Data]()
for i in images {
if let data = i.jpegData(compressionQuality: 1.0) {
dataArray.append(data)
}
}
return dataArray
}
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
Header:
let header = ["Content-Type" : "application/x-www-form-urlencoded", "Authorization" : "Basic " + self.basicAuth];
Body:
var body : [String : AnyObject] = [:];
let body = ["grant_type" : "client_credentials", "scope" : "MessageSender"];
The Request and Serialization:
private func makeHTTPPostRequest(path: String, header: [String : String], body: [String: AnyObject], onCompletion: #escaping ServiceResponse) {
let request = NSMutableURLRequest(url: NSURL(string: path)! as URL)
// Set the method to POST
request.httpMethod = "POST"
do {
// Set the POST body for the request
let jsonBody = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
request.httpBody = jsonBody
let session = URLSession.shared
request.allHTTPHeaderFields = header;
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if let httpResponse = response as? HTTPURLResponse {
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
print(response)
print(json)
onCompletion(json,httpResponse, error as NSError?)
} else {
onCompletion(JSON.null,HTTPURLResponse.init(), error as NSError?)
}
}
})
task.resume()
} catch {
onCompletion(JSON.null,HTTPURLResponse.init(), nil)
}
}
}
When the request is done, it fires a 400 response with
{
"error_description" : "grant_type parameter is requiered field and it has to be non empty string.",
"error" : "invalid_request"
}
Obviously the body is not set correctly but I really don´t know why. I´m using this piece of code in other applications with no problem... .
The same request works like charm in Postman. The body in postman is set with type x-www-form-urlencoded.
Maybe the JSONSerialization is wrong ?
To send a POST request with Content-Type: application/x-www-form-urlencoded;, you need to create a URL query-like String and then convert it to a Data. Your code or any Swift Standard Library functions do not have the functionality. You may need to write it by yourself, or find a suitable third-party library. (Of course JSONSerialization is not suitable here, the String is not a JSON.)
With given a Dictionary<String, String>, you can do it like this:
var body: [String: String] = [:]
body = ["grant_type": "client_credentials", "scope": "MessageSender"]
(Simplified...)
request.httpBody = body.map{"\($0)=\($1)"}.joined(separator: "&").data(using: .utf8)
//`body.map{"\($0)=\($1)"}.joined(separator: "&")` -> grant_type=client_credentials&scope=MessageSender
(Strict... 4.10.22.6 URL-encoded form data)
extension CharacterSet {
static let wwwFormUrlencodedAllowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._*" + "+")
}
extension String {
var wwwFormUrlencoded: String {
return self
.replacingOccurrences(of: " ", with: "+")
.addingPercentEncoding(withAllowedCharacters: .wwwFormUrlencodedAllowed)!
}
}
class HTTPBody {
static func wwwFormUrlencodedData(withDictionary dict: [String: String]) -> Data {
return body
.map{"\($0.wwwFormUrlencoded)=\($1.wwwFormUrlencoded)"}
.joined(separator: "&").data(using: .utf8)!
}
}
request.httpBody = HTTPBody.wwwFormUrlencodedData(withDictionary: body)
(Remember, not many servers interpret the received form data as strictly generated.)
One more, this is not a critical issue in this case, but you should better use Swift classes rather than NS-something:
typealias ServiceResponse = (JSON, HTTPURLResponse?, Error?)->Void
private func makeHTTPPostRequest(path: String, header: [String : String], body: [String: String], onCompletion: #escaping ServiceResponse) {
var request = URLRequest(url: URL(string: path)!)
// Set the method to POST
request.httpMethod = "POST"
// Set the POST body for the request (assuming your server likes strict form data)
request.httpBody = HTTPBody.wwwFormUrlencodedData(withDictionary: body)
let session = URLSession.shared
request.allHTTPHeaderFields = header;
let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in
if let httpResponse = response as? HTTPURLResponse {
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
print(response)
print(json)
onCompletion(json, httpResponse, error)
} else {
onCompletion(JSON.null, httpResponse, error)
}
}
})
task.resume()
}
I'm trying to get an image uploaded to a RESTful web api and I'm getting a HTTP status 415 (unsupported media type). The strange thing is I did have it working the other day and i'm not to sure what i've changed to make it stop accepting the POST request.
My swift code:
func uploadImage(image: UIImage, userId: String, completion: () -> Void) {
parameters["userId"] = userId
let imageData = UIImageJPEGRepresentation(image, 70)
let urlRequest = urlRequestWithComponents(myUrl, parameters: parameters, imageData: imageData!)
Alamofire.upload(urlRequest.0, data: urlRequest.1)
.responseJSON { (response) in
print(response)
if let result = response.result.value as? Dictionary<String, String>{
print("have a result from uploading!")
print(result)
if let result = result["success"] {
if (result == "true") {
completion()
}
}
}
}
}
func urlRequestWithComponents(urlString:String, parameters:Dictionary<String, String>, imageData:NSData) -> (URLRequestConvertible, NSData) {
// create url request to send
let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: urlString)!)
mutableURLRequest.HTTPMethod = Alamofire.Method.POST.rawValue
let boundaryConstant = "myRandomBoundary12345";
let contentType = "multipart/form-data"
mutableURLRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")
// create upload data to send
let uploadData = NSMutableData()
// add image
uploadData.appendData("\r\n--\(boundaryConstant)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData("Content-Disposition: form-data; name=\"file\"; filename=\"file.png\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData("Content-Type: image/png\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData(imageData)
// add parameters
for (key, value) in parameters {
uploadData.appendData("\r\n--\(boundaryConstant)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n\(value)".dataUsingEncoding(NSUTF8StringEncoding)!)
}
uploadData.appendData("\r\n--\(boundaryConstant)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
// return URLRequestConvertible and NSData
return (Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: nil).0, uploadData)
}
And here is what my Jersey Rest service accepts for that url:
#POST
#Path("/uploadImage")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.MULTIPART_FORM_DATA)
public String uploadImage(FormDataMultiPart form) throws IOException
{
//do stuff
}
Its driving me crazy trying to figure out what is going wrong, any help is muchly appreciated!
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)
}
}