I am using a php based web service which accepts post variables. When I run it in postman it works fine but when I run it from Alamofire in my code the web dev says he is receiving the request in an array. Following is the request that server receives:
{"{\"gameDate\":\"2018-03-31_12:43:37\",\"gameFee\":\"55\",\"gameInstruction\":\"\",\"gametitle\":\"ttt\",\"key\":\"AAAA\",\"latitude\":\"\",\"longitude\":\"\",\"numOfPlayers\":20,\"privacy\":0,\"status\":0,\"uid\":\"aaaaa\"}":"","0":""}
Following is how I am sending the request in Alamofire:
Alamofire.request(url, method: .post, parameters: param, encoding: JSONEncoding.default, headers: ["Content-Type":"application/x-www-form-urlencoded"]).responseJSON {
response in
switch response.result {
case .success:
completionHandler(Result.Success(response.result.value))
break
case .failure(let error):
print(error)
completionHandler(Result.Failure(.serverConnectionFailure))
}
}
param:
["key":appKey as AnyObject, "uid":event.uid as AnyObject,"gametitle":event.gameTitle as AnyObject,"gameDate":event.gameDate as AnyObject,"gameFee":event.gameFee as AnyObject,"gameInstruction":event.gameInstruction as AnyObject,"latitude":event.latitude as AnyObject,"longitude":event.longitude as AnyObject,"numOfPlayers":event.numOfPlayers as AnyObject,"privacy":event.privacy as AnyObject, "status":event.status as AnyObject]
How should I convert it to normal key-value request that php service accepts?
Edit:
my postman request that works:
You have to send the params in form of Dictionary(i.e [String: Any]).
To convert class to [String: Any] :-
//Protocol for converting Class to Dictionary
protocol JSONAble {}
extension JSONAble {
func toDict() -> [String : Any] {
var dict = [String : Any]()
let otherSelf = Mirror(reflecting: self)
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value
}
}
return dict
}
}
//Request Parameters Class
class request : JSONAble {
//MARK:- Properties
var gameDate = String()
var gameFee = String()
var gameInstruction = String()
.
.
//MARK:- Constructor
init(gameDate : String?, gameFee: String?, gameInstruction: String?) {
self.gameDate = gameDate
self. gameFee = gameFee
self. gameInstruction = gameInstruction
}
}
Use:-
var params : [String : Any]?
if let jsonableRequest = request as? JSONAble {
params = jsonableRequest.toDict()
}
=> Send this params as request.
Related
I have an API, https://00.00.00.00/api/commitPayment that in the body has amount value. When request sends, server redirect this request to https://00.00.00.00/api/commitPaymentV3. But redirect has empty body. I need to modify redirect and pass amount from request to redirected request. Address in redirect may change, so I can't just sent requests directly to it.
Request
struct TopUpWithAmountRequest: Codable {
let amount: Int
let redirect: Bool
enum CodingKeys: String, CodingKey {
case amount = "Amount"
case redirect = "ShouldRedirect"
}
}
API
import Moya
enum TopUpAPI {
case topUpWithAmount(request: TopUpWithAmountRequest)
}
extension TopUpAPI: TargetType {
var baseURL: URL {
return URL(string: "https://00.00.00.00")!
}
var path: String {
switch self {
case .topUpWithAmount:
return "/api/commitPayment"
}
var method: Moya.Method {
switch self {
case .topUpWithAmount:
return .post
}
var task: Task {
switch self {
case .topUpWithAmount(let request):
return .requestJSONEncodable(request)
}
var headers: [String : String]? {
let headers = ServerConstants.httpHeaders
return headers
}
}
Service
class TopUpService: TopUpServiceProtocol {
let provider = MoyaProvider<TopUpAPI>()
func topUp(request: TopUpWithAmountRequest, completion: #escaping (Result<Response, MoyaError>) -> Void) {
provider.request(.topUpWithAmount(request: request), completion: completion)
}
}
Caller code
let request = TopUpWithAmountRequest(amount: Int(amount), redirect: true)
view?.showProgressHUD()
topUpService.topUpWithRedirector(request: request) { [weak self] result in
switch result {
case .success(let response):
...
case .failure(let error):
print("--- Error in TopUpAmount: \(error)")
self?.view?.showErrorNotification()
...
}
self?.view?.hideProgressHUD()
}
I can't realise, how to modify redirect body with Moya. But I did it with Alamofire:
Alamofire solution:
func topUpWithRedirector(amount: Int, completion: #escaping (DataResponse<TopUpPaymentAFResponse, AFError>) -> Void) {
let newApi = "https://00.00.00.00/api/commitPayment"
let request = AF.request(
newApi,
method: HTTPMethod.post,
parameters: ["Amount" : amount, "ShouldRedirect" : true],
encoding: JSONEncoding.default,
headers: [ "Content-Type" : "application/json", "MobileReq" : "ios" ]
)
let redirector = Redirector(behavior: .modify({ task, request, responce in
var newRequest: URLRequest = request
let parameters: [String: Any] = [
"Amount" : amount
]
let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: [])
newRequest.httpBody = httpBody
return newRequest
}))
request.redirect(using: redirector).responseDecodable(of: TopUpPaymentAFResponse.self, completionHandler: completion)
}
So, I need help to realise this with Moya library. Thanks.
Please help and explain why the router doesn't have responseDecodable. I made a router for AF request call and one of the endpoint need to send up String: [String: Any]. I'm not sure what I did wrong. Thank you!
AFRouter
enum AFRouter: URLRequestConvertible {
case test([String: [String: Any]])
var base: URL {
return URL(string: "https://example.com")!
}
var method: HTTPMethod {
switch self {
case .test:
return .get
}
var path: String {
switch self {
case .test(_):
return "/v2/test"
}
}
func asURLRequest() throws -> URLRequest {
let urlString = baseURL.appendingPathComponent(path).absoluteString.removingPercentEncoding!
let removeSpace = urlString.replacingOccurrences(of: " ", with: "")
let url = URL(string: removeSpace)
var request = URLRequest(url: url!)
request.method = method
switch self {
case .test(_):
guard let token = defaults.string(forKey: "token") else {
return request
}
request.setValue("Bearer " + token , forHTTPHeaderField: "Authorization")
request = try JSONEncoding.default.encode(request)
return request
}
}
Codable
struct Test: Codable {
let success: String
let message: String
let data: [String]
}
Calling API
func getTest(testInfo: [String: Any]) {
AF.request(AFRouter.test(["Testing": testInfo]).responseDecodable(of: Test.self) { response in //got error here "Value of type 'AFRouter' has no member 'responseDecodable'"
//do something...
})
}
The error is saying that you want to use responseDecodable(of:) on a AFRouter instance.
But, in fact, you want to use it on a DataRequest instance.
But it "should work", so are you calling it on a mistaken instance? if we observe, there is a missing ):
AF.request(AFRouter.test(["Testing": testInfo]).responseDecodable(of:...
=>
AF.request(AFRouter.test(["Testing": testInfo])).responseDecodable(of:...
I have an API call which checks for the UserLogin and Password and sends a response based on whether the username and password Pairs are correct or not.
The API returns a dictionary if the username-password pair is incorrect and an array of dictionaries if the username-password is correct.
The problem I face is that I am unable to downcast the response from Alamofire to a particular data type.
func afLoginDriver(firstName: String,password:String,completion: #escaping ([Dictionary<String, Any>])->Void){
let driverLoginURL = "URL here.."
let parameters = ["firstName" : firstName, "Password" : password]
AF.request(driverLoginURL, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil).responseJSON { response in
switch response.result {
case .success:
let driverLoginResponse = response.value as? [Dictionary<String, Any>]
completion(driverLoginResponse)
break
case .failure(let error):
print(error)
}
}
}
The variable driverLoginResponse throws up an error if the username-password pairs is incorrect as it is only a dictionary and not an array of dictionary.
I tried using the guard let statement but still was not able to get this to work.
I would use Codable and enum for that but consider this:
First, implement an enum with any possible response:
enum LoginDriverResponseValue {
case dictionary([String: Any])
case array([[String: Any]])
case string(String)
case unknown(Any)
}
then, change the function signature to adapt with that enum:
func afLoginDriver(firstName: String, password: String, completion: #escaping (LoginDriverResponseValue)->Void) {
,,,
}
And lastly, switch on the enum and call corresponding completion:
guard let responseValue = response.value else { return }
switch responseValue {
case let result as [String: Any]: completion(.dictionary(result))
case let result as [[String: Any]]: completion(.array(result))
case let result as String: completion(.string(result))
default: completion(.unknown(responseValue))
}
- More encapsulated way:
you can encapsulate responseValue type detection into the enum LoginDriverResponseValue:
extension LoginDriverResponseValue {
init(responseValue: Any) {
switch responseValue {
case let result as [String: Any]: self = .dictionary(result)
case let result as [[String: Any]]: self = .array(result)
case let result as String: self = .string(result)
default: self = .unknown(responseValue)
}
}
}
So then the only thing you need in the function will be:
guard let responseValue = response.value else { return }
completion(LoginDriverResponseValue(responseValue: responseValue))
Cast the response values to the potential Types, and handle each:
if let responseOne = response.value as? [String: Any] {
// ...
} else if let responseTwo = response.value as? [[String: Any]] {
// ...
}
You could use Codable:
struct Response: Codable {
var valueOne: [String: Any]?
var valueTwo: [[String: Any]]?
}
I'm Working on a little project using Swift 2.0 and Alamofire 3.
I have few parameters that I need to send to server.
var myParameters = [
"myObjectName":"FooBar",
"myObjectId":123456
]
To send the data I have a basic request written with Alamofire
let myRequest = Alamofire.request(.POST, MY_SERVICE_URL, parameters: myParameters as! [String : AnyObject], encoding: .JSON)
and I'm getting response like this:
myRequest.response { myRequest, myResponse, myData, myError in
/*
Here I would like to access myParameters, simply to double-check the ID of the data that I have sent out with myRequest.
*/
print("I'm Done with sending Object number \(myObjectId).")
}
Is there any simple way to pass myObjectId to response closure? Or access it any other way?
You can retrieve the parameters like this:
let request = Alamofire.request(.POST, "", parameters: parameters, encoding: .JSON, headers: nil)
request.response { (request, response, data, error) -> Void in
guard let httpBody = request?.HTTPBody else { return }
do {
if let jsonParams = try NSJSONSerialization.JSONObjectWithData(httpBody, options: []) as? [String: AnyObject] {
if let objectId = jsonParams["myObjectId"] {
print(objectId)
}
}
} catch {
print(error)
}
}
I am trying out the Alamofire helpers for networking with my server. I am building up a router for handling my API endpoints. The construction itself seems clear to me, but I am struggling with some SWIFT syntax.
enum Router:URLRequestConvertible {
static let baseURLString = "url"
case AEDS
var URLRequest: NSURLRequest {
let (path: String, parameters: [String: AnyObject]) = {
switch self {
case .AEDS:
let params = [""]
return("/aeds", 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
}
}
I get the message that inside my case .AEDs the params are throwing an error:
[String] is not convertible to [String: AnyObject]
I am kind of new to Swift and could not figure out so far, where to start. I think I provided the array that I am defining. So what does this error mean?
In your switch case, you need to defines params as a dictionary and not as an array.
switch self {
case .AEDS:
let params = [""] <---- This is initialising an array containing a string
return("/aeds", params)
}
Try changing to:
switch self {
case .AEDS:
let params = ["" : ""] <---- This will create a dict
return("/aeds", params)
}
That should solve your problem.