How to post simple string (not dictionary) in alamofire? - swift

I have to post raw string at body.
In the usual case, I did this.
let parameters = ["asdf": "asdf", "fdsa", "fdsa"]
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON.......
but how to post raw string? (it's not json string. just string.)
AF.request(url, method: .post, parameters: "jsut simple string", encoding: JSONEncoding.default).responseJSON.......
How can I do that??

Alamofire 5 now supports Encodable types as parameters. If you want to encode just a String, switch to using that form of making a Request:
AF.request(url, method: .post, parameters: "just simple string", encoder: JSONParameterEncoder.default)
Note the encoder instead of encoding parameter name and new JSONParameterEncoder type.
Also, using responeJSON is no longer recommended, using responseDecodable to produce Decodable types would be a better approach.

You can use below custom encoding to send single value in param, and pass [:] empty dictionary in parameter
struct SingleValueEncoding: ParameterEncoding {
private let value: String
init(value: String) {
self.value = value
}
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = urlRequest.urlRequest
let data = value.data(using: .utf8)!
if urlRequest?.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest?.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest?.httpBody = data
return urlRequest!
}
}

In Alamofire request, the Parameters is dictionary
/// A dictionary of parameters to apply to a `URLRequest`.
public typealias Parameters = [String: Any]

Related

Alamofire, HTTPheaders for post request [string:any]

I need to send a post request using alamofire to my server, one of the header to be sent is not a string value but is an Int
Reading the documentation of Alamofire look like the HTTPHeaders is only type [String: String]
Is there any way to customise the HTTPHeaders to [String:Any]?
I can't find to much understandable for me online.
thanks
Alamofire doesn't have such methods, but you can easily do it
["hey": 1].mapValues { String(describing: $0) } returns [String: String]
If you have many places where you're using it, you can:
Create extension for Dictionary
extension Dictionary where Key == String, Value == Any {
func toHTTPHeaders() -> HTTPHeaders {
HTTPHeaders(mapValues { String(describing: $0) })
}
}
// Usage
AF.request(URL(fileURLWithPath: ""), headers: ["": 1].toHTTPHeaders())
Create extension for HTTPHeaders
extension HTTPHeaders: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, Any)...) {
self.init()
elements.forEach { update(name: $0.0, value: String(describing: $0.1)) }
}
}
// usage
AF.request(URL(fileURLWithPath: ""), headers: HTTPHeaders(["": 1]))
Create extension for Session
extension Session {
open func request(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: [String: Any],
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil) -> DataRequest {
return request(convertible, method: method, parameters: parameters, encoding: encoding, headers: headers.mapValues { String(describing: $0) }, interceptor: interceptor, requestModifier: requestModifier)
}
}
// Usage
AF.request(URL(fileURLWithPath: ""), headers: ["": 1])
The reason there's no such option in Alamofire is type safety. When you use Any you can literary pass any value there and so probability of a mistake is much more. By requiring string library makes sure you're converting all values you need by yourself.
I'd go for the first variant, because it's more clear when you read the code that there's something going on there

Can't make post request using params as dictionary with Swift 4 & Alamofire

I'm trying to learn to call API with/without library. But the problem here confuses me.
I have params like this:
let parameters: [String:String] =
["key":"MY_KEY" ,
"q":sourceText,
"source": sourceLanguage),
"target": target)]
let headers: HTTPHeaders = [ "Content-type": "application/json"]
I make a post call like this:
Alamofire.request(urlString, method: HTTPMethod.post, parameters: parameters, headers: headers)
.responseJSON{ response in
guard response.result.error == nil else {
print("error calling POST on /todos/1")
print(response.result.error!)
return
}
// make sure we got some JSON since that's what we expect
guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
print("Error: \(response.result.error)")
return
}
By this I get an error 403, saying that I do not have a valid API key (I tested the key with postman, and it is okay).
After many efforts, I have to change the code like this
let stringparams = "key=MY_KEY&q=\(sourceText)&source=\(sourceLanguage)&target=\(target)"
request.httpBody = stringparams.data(using: String.Encoding.utf8)
and using this: Alamofire.request(request)
it works!
I'm using Google Cloud Translation api. And the web use a REST api as said here: https://cloud.google.com/translate/docs/reference/translate
So why can't I use params as dictionary, but using the string (like formdata) will work here?
My guess is Alamofire didn't make the right BODY for the request from the parameters because other arguments is okay. But I don't know why.
And I think Google should accept a json params as they mentioned, in stead of using form data? I did try the original method, but it didn't work with JSON.
From what actually works for you it looks like you need to encode the parameters in the same style as a query. Try this:
struct ParameterQueryEncoding: ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try urlRequest.asURLRequest()
request.httpBody = parameters?
.map { "\($0)=\($1)" }
.joined(separator: "&")
.data(using: .utf8)
return request
}
}
You should then be able to perform the original call that you had before:
Alamofire.request(urlString,
method: HTTPMethod.post,
parameters: parameters,
encoding: ParameterQueryEncoding(),
headers: headers)
.responseJSON { response in
...
}
Try by using JSON encoding. Make sure you have removed ) from dictionary.
Alamofire.request(URL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)

Alamofire 5 Escaping Forward Slashes

Ive been googling and trying for the last few days regarding the automatic escaping of forward slashes of alamofire.
(Where "/path/image.png" becomes "\/path\/image.png")
However all the answers either point towards a solution if your using swiftyJson, sending via a httpBody or using the Alamofire Parameter Class.
https://github.com/SwiftyJSON/SwiftyJSON/issues/440
Im not using SwiftyJson and feel to install the API just to resolve the issue is a case of hitting the nail with a sledge hammer.
Anyway.
My issue is whenever I'm trying to send parameters to an API, Alamofire's
JSONEncoding.default kindly escapes forward slashes. I don't want Alamofire to do this.
In my case I want to send the following parameter and have Alamofire ignore the forward slashes
let parameter : Parameters = ["transaction": "/room/120"]
Alamofire.request(endPoint , method: .post, parameters: parameter ,encoding: JSONEncoding.default , headers: header).validate(statusCode: 200..<300).responseObject { (response: DataResponse<SomeModel>) in
}
Maybe creating a custom json encoder is the way to do this but I find very little documentation on how best to go about it.
I have also tried
let parameter : [String : Any] = ["transaction": "/room/120"]
Alamofire.request(endPoint , method: .post, parameters: parameter ,encoding: JSONEncoding.default , headers: header).validate(statusCode: 200..<300).responseObject { (response: DataResponse<SomeModel>) in
Really appreciate all your help and suggestions.
Ive even read that the backend should be able to deal with the escaping characters, but its working fine for the android dev. Thus if its my code that is sending the wrong data, then I feel it should resolved at the source
Thomas
I had the same issue and decided to go the way of the custom JSON encoder. There are probably better/shorter ways than this but seeing as I'm a Swift noob, it does it's job and is good enough for me.
I simply looked up the used JSONEncoder used by Alamofire and made my own:
public struct JSONEncodingWithoutEscapingSlashes: ParameterEncoding {
// MARK: Properties
/// Returns a `JSONEncoding` instance with default writing options.
public static var `default`: JSONEncodingWithoutEscapingSlashes { return JSONEncodingWithoutEscapingSlashes() }
/// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options.
public static var prettyPrinted: JSONEncodingWithoutEscapingSlashes { return JSONEncodingWithoutEscapingSlashes(options: .prettyPrinted) }
/// The options for writing the parameters as JSON data.
public let options: JSONSerialization.WritingOptions
// MARK: Initialization
/// Creates a `JSONEncoding` instance using the specified options.
///
/// - parameter options: The options for writing the parameters as JSON data.
///
/// - returns: The new `JSONEncoding` instance.
public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
// MARK: Encoding
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue)?.replacingOccurrences(of: "\\/", with: "/")
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = string!.data(using: .utf8)
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
/// Creates a URL request by encoding the JSON object and setting the resulting data on the HTTP body.
///
/// - parameter urlRequest: The request to apply the JSON object to.
/// - parameter jsonObject: The JSON object to apply to the request.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let jsonObject = jsonObject else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue)?.replacingOccurrences(of: "\\/", with: "/")
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = string!.data(using: .utf8)
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
Probably should include some more error handling too :)
Finally I could use it like the standard JSONEncoder:
Alamofire.request(EndpointsUtility.sharedInstance.cdrStoreURL, method: .post, parameters: payload, encoding: JSONEncodingWithoutEscapingSlashes.prettyPrinted)...
hope it helps!

Swift 4 generics with struct

I'm trying to use generics with Codable protocol, but I'm getting an error.
Cannot invoke 'decode' with an argument list of type '([T.Type], from: Data)
static func getRequest<T>(model: T.Type, url: String, parameters: [String: Any]? = nil, headers: [String: String]? = nil, data: #escaping (Any?, Any?, Error?) -> ()) -> Alamofire.DataRequest {
return Alamofire.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers)
.validate(contentType: [RequestHelper.HeaderKeys.contentTypeJson])
.responseJSON { (response) in
print(response)
switch response.result {
case .success:
let responseData = response.data!
do {
print(model)
print(T.self)
let decodable = try JSONDecoder().decode([model].self, from: responseData)
data(response.response?.allHeaderFields, decodable, nil)
} catch let error {
data(nil, nil, error)
}
case .failure(let requestError):
data(nil, nil, requestError)
print(requestError)
}
}
}
I need pass my struct model to this method
How can I fix this? Does anyone could help me?
decode() can only take a type that is Decodable. You need to specify that in your method signature. Either add where T: Decodable at the end of getRequest's declaration or just put <T: Decodable> inside the brackets to restrict T to decodable types only, and then you should be able to pass your parameter to decode().
EDIT: Looking at your code, there's another error:
let decodable = try JSONDecoder().decode([model].self, from: responseData)
Instead of [model].self, you need to pass [T].self. Otherwise you're passing an array of types rather than the type of an array.

HTTP Request error using Alamofire

I am trying to access my MAMP database webservice using Alamofire. Following is my code:
Following is my router to construct my URL:
enum Router: URLRequestConvertible {
static let baseURLString = "http://pushchat.local:44447/"
case PostJoinRequest(String,String,String,String,String)
var URLRequest: NSURLRequest {
let (path: String, parameters: [String: AnyObject]) = {
switch self {
case .PostJoinRequest (let addPath, let userID, let token, let nickName, let secretCode):
let params = ["cmd": "join", "user_id": "\(userID)", "token": "\(token)", "name": "\(nickName)", "code": "\(secretCode)"]
return (addPath, params)
}
}()
let URL = NSURL(string: Router.baseURLString)
let URLRequest = NSURLRequest(URL: URL!.URLByAppendingPathComponent(path))
let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(URLRequest, parameters: parameters).0
}
}
Following is my viewdidload code:
Alamofire.request(.POST,Router.PostJoinRequest("api.php","12345678901234","12345678901234","ABCDEF","TopSecret")).responseJSON()
{(request, response, JSON, error) in
println(JSON)
}
Following is the compile error:
Cannot invoke 'responseJSON' with an argument list of type '((,,,)->_)'
Following is the declaration from Almofire for your reference.
:param: method The HTTP method.
:param: URLString The URL string.
:param: parameters The parameters. `nil` by default.
:param: encoding The parameter encoding. `.URL` by default.
:returns: The created request.
*/
// public func request(method: Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
// return request(encoding.encode(URLRequest(method, URLString), parameters: parameters).0)
// }
Please let me know why am I facing this issue while chaining and what is it that I am not doing right?
Thanks for your help.
Dev
The compiler error message is really misleading – there is no problem with responseJSON but with request method itself.
In fact compiler does not like your second parameter. You are passing URLRequestConvertible but URLStringConvertible is expected (see the signature you posted).
Maybe you wanted to call another version of request method:
//:param: URLRequest The URL request
//:returns: The created request.
public func request(URLRequest: URLRequestConvertible) -> Request
In that case you have to adjust your Router class and set HTTP method into NSURLRequest created inside. For example:
let URLRequest = NSMutableURLRequest(URL: URL!.URLByAppendingPathComponent(path))
URLRequest.HTTPMethod = "POST"
Note you will also probably need to use another parameter/data encoding.