Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alamofire response object mapping

I am an android developer new to swift 3 programming, I am using Alamofire for making api calls and to avoid tedious json paring I am using AlamofireObjectMapper library. I have a ApiController which has a function to make api calls below 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 am getting from server is:

{
 "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 is the function to class make 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 in the below line

let responseObject = responseClass(JSONString: jsonString!)

I am not able to understand how to convert jsonString into the responseClass generic object which I am accepting from View controller

Someone please help me resolve this, stuck on this issue for quite a while now.

like image 908
Syn3sthete Avatar asked Jun 01 '17 21:06

Syn3sthete


2 Answers

You can use AlamofireMapper:

With json:

{
   "page":1,
   "per_page":3,
   "total":12,
   "total_pages":4,
   "data":[
      {
         "id":1,
         "first_name":"George",
         "last_name":"Bluth",
         "avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"
      },
      {
         "id":2,
         "first_name":"Janet",
         "last_name":"Weaver",
         "avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
      },
      {
         "id":3,
         "first_name":"Emma",
         "last_name":"Wong",
         "avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/olegpogodaev/128.jpg"
      }
   ]
}

Swift class:

class UserResponse: Decodable {
    var page: Int!
    var per_page: Int!
    var total: Int!
    var total_pages: Int!

    var data: [User]?
}

class User: Decodable {
    var id: Double!
    var first_name: String!
    var last_name: String!
    var avatar: String!
}

Request with alamofire

let url1 = "https://raw.githubusercontent.com/sua8051/AlamofireMapper/master/user1.json"
        Alamofire.request(url1, method: .get
            , parameters: nil, encoding: URLEncoding.default, headers: nil).responseObject { (response: DataResponse<UserResponse>) in
                switch response.result {
                case let .success(data):
                    dump(data)
                case let .failure(error):
                    dump(error)
                }
        }

Link: https://github.com/sua8051/AlamofireMapper

like image 135
Sua Le Avatar answered Nov 11 '22 08:11

Sua Le


Generic Response Object Serialization using Swift 4 Codable

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


Following is a typical model which we use to deserialize JSON data with generics using Alamofire. There is plenty of 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 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 of the work in deserialization JSON data and handle errors. Using Swift extensions we add functions to decode incoming JSON into our model struct/class that we will write afterward.

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.

Helper 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 earlier mentioned “using Swift 4 Codable” but if all we want is to decode JSON from the server, we only need is a model struct/class that conforms to protocol Decodable. (If you have the same structure you want to upload you can use Codable to handle both decoding and encoding) So, now our User model struct now looks like this.

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) }"
    }
}

Lastly, our function call to API looks like.

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 only modifications you need in model struct/class is that all structs/classes must conform to Decodable protocol and Swift takes care of everything else.

like image 25
harsh_v Avatar answered Nov 11 '22 09:11

harsh_v