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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With