jackdm
jackdm

Reputation: 329

POST Json to API with Alamofire?

I want to post a JSON object I create in my service class and pass to the networkService.

This is my network service, but i get an error of

Value of type '[String : Any]' has no member 'data'

on the line: let jsonData = json.data(using: .utf8, allowLossyConversion: false)!

    func request(json: [String:Any]) {

    let url = URL(string: urlString)!
    let jsonData = json.data(using: .utf8, allowLossyConversion: false)!

    var request = URLRequest(url: url)
    request.httpMethod = HTTPMethod.post.rawValue
    request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
    request.httpBody = jsonData

    Alamofire.request(request).responseJSON {
        (response) in
        print(response)
    }
}

The idea being I pass in my JSON when i call the func via the func parameter.

This is the JSON object passed in:

    func loginUser(data: Array<String>, deviceToken: String) {
    // create JSON
    let json = [ "login-email" : data[0],
                "login-password" : data[1],
                "login-secret" : "8A145C555C43FBA5",
                "devicetoken" : deviceToken
                ]

    networkManager.request(json: json)
}

Then I convert and send it to the API (urlString)

Any idea if/why this isnt working?

THanks

Updated revision:

func request(json: [String:Any]) {

    let url = URL(string: urlString)!

    do {
        let jsonData = try JSONSerialization.data(withJSONObject: json, options:[])
        var request = URLRequest(url: url)
        request.httpMethod = HTTPMethod.post.rawValue
        request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
        request.httpBody = jsonData

        Alamofire.request(request).responseJSON {
            (response) in
            print(response)
        }
    } catch {
        print("Failed to serialise and send JSON")
    }
}

update: added my code to make a call with completion question:

 func sendLoginRequest() {
        let userLogin = UserService.init(loginEmail: userEmail, loginPassword: userPassword, loginSecret: loginSecret, deviceToken: deviceToken)
        networkService.logUserIn(request: userLogin) { (<#JSON?#>, <#NSError?#>) in
            <#code#>
        }
    }

edit: Updated Payload Shot:

API Payload Issue

edit 2: mapping issue example:

init?(_ json: JSON) {
    // Map API Key from top level
    guard let apiKey = json["apikey"].string else { return nil }

    // Map User at user level
    guard let userDataArray = json["user"].array else {
        fatalError("user data array NOT FOUND")
    }
    print("USER DATA IS \(userDataArray)")
    // assign user 
    for child in userDataArray {
        guard let userID = child["id"].int,
            let userEmail = child["email"].string,
            let lastName = child["lastname"].string,
            let firstName = child["firstname"].string,
            let company = child["company"].string,
            let userImage = child["image"].string,
            let jobTitle = child["jobtitle"].string
            else { return nil
        }
    }

    // Assign to model properties
    self.apiKey = apiKey
    self.userEmail = userEmail
    self.lastName = lastName
    self.firstName = firstName
    self.company = company
    self.userImage = userImage
    self.jobTitle = jobTitle
    self.userID = userID
}

Upvotes: 3

Views: 7407

Answers (1)

Aleksandr Honcharov
Aleksandr Honcharov

Reputation: 2513

I just show how I work with this.

You don't have to convert your parameters to JSON. It's code from Alamofire.

/// A dictionary of parameters to apply to a `URLRequest`.
public typealias Parameters = [String: Any]

Use this method instead of your:

Alamofire.request(url, method: method, parameters: parameters, encoding: encoding, headers: customHeaders)

Try this: Instead of your request.httpBody = jsonData you can pass your json in parameters.

Your whole code will be:

func request(json: [String:Any]) {

    Alamofire.request(urlString, method: .post, parameters: json, encoding: JSONEncoding.default).responseJSON {
        (response) in
        print(response)
    }

}

If you are interested in my approach:

func makePick(request: MakePickRequest, completionHandler: @escaping APICompletionHandler) {
        let parameters = request.converToParameters()
        Alamofire.request(Endpoints.makePick, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
            self.handleResponse(response: response, completionHandler: completionHandler)
        }
    }

Request:

struct MakePickRequest: GeneralRequest {
    let eventId: Int64
    let sportId: String
    let pickType: PickType
    let betType: BetType
    let amount: Int

    func converToParameters() -> [String : String] {
        return ["event_id": String(eventId), "sport_id": sportId,
        "pick_type": pickType.rawValue, "bet_type": betType.rawValue,
        "amount": String(amount)]
    }
}

Structure with endpoints:

struct Endpoints {
    // Development baseURL
    static let baseURL = "http://myurl/"

    private static let apiVersion = "api/v1/"

    static var fullPath: String {
        return "\(baseURL)\(apiVersion)"
    }

    // MARK: - User endpoints (POST)
    static var login: String {
        return "\(fullPath)users/login"
    }

    static var signUp: String {
        return "\(fullPath)users/signup"
    }

    ...
}

Outside of any class (but import SwiftyJSON is obligatory):

typealias APICompletionHandler = (_ data: JSON?, _ error: NSError?) -> Void

Handle response:

private func handleResponse(response: DataResponse<Any>, completionHandler: APICompletionHandler) {
        self.printDebugInfo(response)
        switch response.result {
        case .success(let value):
            self.handleJSON(data: value, handler: completionHandler)
        case .failure(let error):
            print(error)
            completionHandler(nil, error as NSError?)
        }
    }

 private func handleJSON(data: Any, handler: APICompletionHandler) {
        let json = JSON(data)
        let serverResponse = GeneralServerResponse(json)
        if (serverResponse?.status == .ok) {
            handler(serverResponse?.data, nil)
        } else {
            handler(nil, self.parseJsonWithErrors(json))
        }
    }

GeneralServerResponse (depends on your server API):

import SwiftyJSON

final class GeneralServerResponse {
    let data: JSON
    let status: Status

    init?(_ json: JSON) {
        guard let status = json["status"].int else {
            return nil
        }

        self.status = Status(status)
        self.data = json["data"]
    }

    enum Status {
        case ok
        case error
        case unauthorized

        init(_ input: Int) {
            if input >= 200 && input < 400 {
                self = .ok
            } else if input == 403 {
                self = .unauthorized
            } else {
                self = .error
            }
        }
    }
}

My actual example of usage.

This is outside:

func +=<K, V> ( left: inout [K : V], right: [K : V]) { for (k, v) in right { left[k] = v } }

Example of request:

func makePick(request: MakePickRequest, completionHandler: @escaping APICompletionHandler) {
        var parameters = ["auth_token": Preferences.getAuthToken()]
        parameters += request.converToParameters()
        manager.apiRequest(url: Endpoints.makePick, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
            self.handleResponse(response: response, completionHandler: completionHandler)
        }
    }

SessionManager extension to add headers for all requests:

extension SessionManager {
    func apiRequest(url: URLConvertible, method: HTTPMethod, parameters: Parameters? = nil, encoding: ParameterEncoding, headers: HTTPHeaders? = nil)  -> DataRequest {
        var customHeaders: HTTPHeaders = ["api-key" : "1wFVerFztxzhgt"]
        if let headers = headers {
            customHeaders += headers
        }
        return request(url, method: method, parameters: parameters, encoding: encoding, headers: customHeaders)
    }
}

In APIManager class:

private let manager: SessionManager

init() {
     manager = Alamofire.SessionManager.default
}

Call example:

apiClient.makePick(request: request) { data, error in
        if let error = error {
            print(error.localizedDescription)
            return
        }
        if let data = data {
            // data is a JSON object, here you can parse it and create objects
        }
    }

Example of class:

import SwiftyJSON

final class MyClass {
    let id: Int
    let username: String
    let parameter: Double

    init?(_ json: JSON) {
        guard let id = json["id"].int, let username = json["username"].string,
            let parameter = json["parameter"].double else {
                return nil
        }

        self.id = id
        self.username = username
        self.parameter = parameter
    }
}

Upvotes: 7

Related Questions