Displaying the Alamofire response object

I am an Android developer for quick 3 programming, I am using Alamofire to make api calls and avoid the tedious json paring I am using the AlamofireObjectMapper library. I have ApiController

one that has a function to make the api calls below - this is the code for that:

public static func makePostRequest<T: Mappable>(url: String, params: Parameters, networkProtocol: NetworkProtocol, responseClass: T){

    let headers = getHeaders()

    networkProtocol.showProgress()

    Alamofire.request(url, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers)
        .validate()
        .responseData{ response in
            let json = response.result.value
            var jsonString = String(data: json!, encoding: String.Encoding.utf8)
            let responseObject = responseClass(JSONString: jsonString!)
            switch(response.result){
            case .success(_):
                networkProtocol.hideProgress()
                networkProtocol.onResponse(response: response)
                break
            case .failure(_):
                networkProtocol.hideProgress()
                networkProtocol.onErrorResponse(response: response)
                break
            }

    }

      

The Json response template I get from the server:

{
 "some_int": 10, 
 "some_array":[...]
}

      

Below is my model class:

import ObjectMapper

    class BaseResponse: Mappable {
    var some_int: Int?
    var some_array: [Array_of_objects]?

    required init?(map: Map) {
        some_int <- map["some_int"]
        some_array <- map["some_array"]
    }

    func mapping(map: Map) {

    }
}

      

And below the class function makes the api call:

public static func callSomeApi(params: Parameters, networkProtocol: NetworkProtocol){
    ApiHelper.makePostRequest(url: AppConstants.URLs.API_NAME, params: params, networkProtocol: networkProtocol, responseClass: BaseResponse)
}

      

Now the error is on the line below

let responseObject = responseClass(JSONString: jsonString!)

      

I cannot figure out how to convert jsonString

to a generic object responseClass

which I am taking from the View controller

Someone please help me resolve this by sticking with this issue for quite some time now.

+3


source to share


3 answers


Serializing Bulk Response Objects using Swift 4 Codable

If you don't want to use another dependency like ObjectMapper you can do the following way, but you might have to do some chagnes.


The following is the typical model we use to deserialize JSON data using generics using Alamofire. There are many examples and excellent documentation on Alamofire.

struct User: ResponseObjectSerializable, ResponseCollectionSerializable, CustomStringConvertible {
    let username: String
    let name: String

    var description: String {
        return "User: { username: \(username), name: \(name) }"
    }

    init?(response: HTTPURLResponse, representation: Any) {
        guard
            let username = response.url?.lastPathComponent,
            let representation = representation as? [String: Any],
            let name = representation["name"] as? String
        else { return nil }

        self.username = username
        self.name = name
    }
}

      

Using the Codable protocol introduced in Swift 4

typealias Codable = Decodable & Encodable

      


The first step in this direction is to add helper functions that will do half the work of deserializing the JSON data and handling the error. Using Swift extensions, we add functions to decode incoming JSON to our model structure / class, which we will write later.

let decoder = JSONDecoder()
let responseObject = try? decoder.decode(T.self, from: jsonData)
The decoder (1) is an object that decodes instances of a data type from JSON objects.

      



Secondary functions

extension DataRequest{
    /// @Returns - DataRequest
    /// completionHandler handles JSON Object T
    @discardableResult func responseObject<T: Decodable> (
        queue: DispatchQueue? = nil ,
        completionHandler: @escaping (DataResponse<T>) -> Void ) -> Self{

        let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
            guard error == nil else {return .failure(BackendError.network(error: error!))}

            let result = DataRequest.serializeResponseData(response: response, data: data, error: error)
            guard case let .success(jsonData) = result else{
                return .failure(BackendError.jsonSerialization(error: result.error!))
            }

            // (1)- Json Decoder. Decodes the data object into expected type T
            // throws error when failes
            let decoder = JSONDecoder()
            guard let responseObject = try? decoder.decode(T.self, from: jsonData)else{
                return .failure(BackendError.objectSerialization(reason: "JSON object could not be serialized \(String(data: jsonData, encoding: .utf8)!)"))
            }
            return .success(responseObject)
        }
        return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
    }

     /// @Returns - DataRequest
    /// completionHandler handles JSON Array [T]
    @discardableResult func responseCollection<T: Decodable>(
        queue: DispatchQueue? = nil, completionHandler: @escaping (DataResponse<[T]>) -> Void
        ) -> Self{

        let responseSerializer = DataResponseSerializer<[T]>{ request, response, data, error in
            guard error == nil else {return .failure(BackendError.network(error: error!))}

            let result = DataRequest.serializeResponseData(response: response, data: data, error: error)
            guard case let .success(jsonData) = result else{
                return .failure(BackendError.jsonSerialization(error: result.error!))
            }

            let decoder = JSONDecoder()
            guard let responseArray = try? decoder.decode([T].self, from: jsonData)else{
                return .failure(BackendError.objectSerialization(reason: "JSON array could not be serialized \(String(data: jsonData, encoding: .utf8)!)"))
            }

            return .success(responseArray)
        }
        return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
    }
 }

      


Second , I previously mentioned "using Swift 4 Codable", but if we all want to decode JSON from the server, we only need a struct / class model that conforms to the Decodable protocol. (If you have one you want to load, you can use Codable to handle both decoding and encoding) So now our user model structure looks like this now.

struct User: Decodable, CustomStringConvertible {
    let username: String
    let name: String

    /// This is the key part
    /// If parameters and variable name differ
    /// you can specify custom key for mapping "eg. 'user_name'"

    enum CodingKeys: String, CodingKey {
        case username = "user_name"
        case name
    }

    var description: String {
        return "User: { username: \(username), name: \(name) }"
    }
}

      


Finally , our API function call looks like this.

Alamofire.request(Router.readUser("mattt"))).responseObject{ (response: DataResponse<User>) in

            // Process userResponse, of type DataResponse<User>:
            if let user = response.value {
                print("User: { username: \(user.username), name: \(user.name) }")
            }
}

      


For more complex (nested) JSON, the logic remains the same, and the only modifications you need to the struct / class model is that all structs / classes must conform to the Decodable protocol and Swift takes care of the rest.

+1


source


You can use SwiftyJSON: https://cocoapods.org/pods/SwiftyJSON

Here's some sample code that might help you:



Alamofire.request(endpointURL, method: .get, parameters: params, encoding: URLEncoding.default, headers: nil).validate().responseJSON()
 {
        (response) in

        if response.result.isFailure
        {
            print("ERROR! Reverse geocoding failed!")
        }
        else if let value = response.result.value
        {
           var country: String? = nil
           var county: String? = nil
           var city: String? = nil
           var town: String? = nil
           var village: String? = nil

           print("data: \(value)")

           let json = JSON(value)

           print("json: \(json)")

           country = json["address"]["country"].string
           county = json["address"]["county"].string
           city = json["address"]["city"].string
           town = json["address"]["town"].string
           village = json["address"]["village"].string
        }
        else
        {
            print("Cannot get response result value!")
        }

 }

      

Please advise that the code has been simplified (many lines removed) and pasted here from my actual project, so this code is not tested, maybe contains typos or something like that, but the logic is visible

0


source


For object mapping, you need to follow this with AlamofireObjectMapper .

//Declare this before ViewLoad
var BaseResponse: Array<BaseResponse>?

// After you receive response  from API lets say "data"
 if let jsonData = data as? String {
      self.BaseResponse = Mapper< BaseResponse >().mapArray(JSONString: jsonData)
}

      

0


source







All Articles