Generic DataResponse with Alamofire and ObjectMapper - swift

I want to create a custom class for Alamofire so I will be able to call the request over and over again in various classes. In order to do that I need to pass the model class that extends to Mappable in DataResponse. But I got many errors.
This is my current code in the custom class:
func callAPI<T: Mappable>(response: T){
Alamofire.request(url!, method: .post, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseObject { (response: DataResponse<T>) in
let result = response.result.value as! BaseData
}
}
And this is my code when i call the method:
func data() {
let manager = ManagerData(url: url)
manager.callAPI(response: Model.self)
}
This is my Model class:
class Model: BaseData {
var point: String?
var list: [Voucher]?
override func mapping(map: Map) {
super.mapping(map: map)
totalPoint <- map["TOTAL_POINT"]
list <- map["VOUCHER"]
}
}
class BaseData: Mappable {
var status: String?
var message: String?
required init() {
}
required init?(map: Map) {
}
func mapping(map: Map) {
status <- map["STATUS"]
message <- map["MESSAGE"]
}
}
On manager.callAPI(response: Model.self) error is Argument type Model.Type does not conform to expected type Mappable.
Any suggestion how to do it?

Related

ObjectMapper failed to serialize response. From API Response using AlamofireObjectMapper

In swift, I am using AlamofireObjectMapper. But I am getting error "ObjectMapper failed to serialize response." here is my api response and model please give me the solution. In response, not any null value.
This is API response
{"status":true,"result":[{"id":"246","sortname":"ZW","name":"Zimbabwe","phonecode":"263"},{"id":"245","sortname":"ZM","name":"Zambia","phonecode":"260"},{"id":"202","sortname":"ZA","name":"South Africa","phonecode":"27"},{"id":"244","sortname":"YU","name":"Yugoslavia","phonecode":"38"},{"id":"141","sortname":"YT","name":"Mayotte","phonecode":"269"},{"id":"243","sortname":"YE","name":"Yemen","phonecode":"967"},{"id":"91","sortname":"XU","name":"Guernsey and Alderney","phonecode":"44"}]}
This is calling API
Alamofire.request(geturl, method: .get).responseArray { [weak self] (response: DataResponse<[countrymodel]>) in
print(response)
switch response.result {
case .success(let assignments):
let realm = try! Realm()
try! realm.write {
realm.delete(realm.objects(countrymodel.self))
realm.add(assignments, update: true)
}
for a in assignments{
print(a)
}
case .failure(let error):
print(".... . .. . .. . .")
}
}
Here is my Model class
import UIKit
import RealmSwift
import ObjectMapper
class countrymodel: Object, Mappable{
enum Status: Int, CustomStringConvertible {
case open, closed, cancelled
var description : String {
switch self {
case .open: return "Open"
case .closed: return "Closed"
case .cancelled: return "Cancelled"
}
}
}
#objc dynamic var id = 0
#objc dynamic var sortname = ""
#objc dynamic var name = ""
#objc dynamic var phonecode = ""
override static func primaryKey() -> String? {
return "id"
}
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
id <- (map["id"])
sortname <- map["sortname"]
name <- map["name"]
phonecode <- map["phonecode"]
}
}
Your response isn't an array its a dictionary and inside it there is an array called result , so the serialization fails , you may make it
Alamofire.request(geturl, method: .get).responseJSON
and parse that array only
You can try this code:
change this code
params).responseArray(completionHandler: { (response: DataResponse<[countrymodel]>) in
like this:
params).responseObject {(response: DataResponse<countrymodel>) in

Using Object Mapping with Kinvey

I have an array of objects I'm trying to get out of one of my collections. I've followed along using their docs and also some Googling and I believe I'm close to the solution, however not close enough. Here's what I have:
class Clothing: Entity {
var categories: [Category]!
var gender: String!
override class func collectionName() -> String {
//return the name of the backend collection corresponding to this entity
return "categories"
}
override func propertyMapping(_ map: Map) {
super.propertyMapping(map)
categories <- map["clothing"]
gender <- map["gender"]
}
}
class Category: NSObject, Mappable{
var title: String?
var image: String?
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
title <- map["category"]
image <- map["image"]
}
}
I'm able to get the right gender, but the array of categories doesn't seem to get mapped to the Category object. Any thoughts?
your model actually have one issue, as you can see at https://devcenter.kinvey.com/ios/guides/datastore#Model you should use let categories = List<Category>() instead of var categories: [Category]!. Here's the model that and test and worked:
import Kinvey
class Clothing: Entity {
let categories = List<Category>()
var gender: String!
override class func collectionName() -> String {
//return the name of the backend collection corresponding to this entity
return "clothing"
}
override func propertyMapping(_ map: Map) {
super.propertyMapping(map)
categories <- ("categories", map["categories"])
gender <- ("gender", map["gender"])
}
}
class Category: Object, Mappable{
var title: String?
var image: String?
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
title <- ("category", map["category"])
image <- ("image", map["image"])
}
}
and here's a sample code how to save a new Clothing object
let casualCategory = Category()
casualCategory.title = "Casual"
let shirtCategory = Category()
shirtCategory.title = "Shirt"
let clothing = Clothing()
clothing.gender = "male"
clothing.categories.append(shirtCategory)
clothing.categories.append(casualCategory)
dataStore.save(clothing) { (result: Result<Clothing, Swift.Error>) in
switch result {
case .success(let clothing):
print(clothing)
case .failure(let error):
print(error)
}
}

How can I do array mapping with objectmapper?

I have a response model that looks like this:
class ResponseModel: Mappable {
var data: T?
var code: Int = 0
required init?(map: Map) {}
func mapping(map: Map) {
data <- map["data"]
code <- map["code"]
}
}
If the json-data is not an array it works:
{"code":0,"data":{"id":"2","name":"XXX"}}
but if it is an array, it does not work
{"code":0,"data":[{"id":"2","name":"XXX"},{"id":"3","name":"YYY"}]}
My mapping code;
let apiResponse = Mapper<ResponseModel>().map(JSONObject: response.result.value)
For details;
I tried this code using this article : http://oramind.com/rest-client-in-swift-with-promises/
you need to use mapArray method instead of map :
let apiResponse = Mapper<ResponseModel>().mapArray(JSONObject: response.result.value)
What I do is something like this:
func mapping(map: Map) {
if let _ = try? map.value("data") as [Data] {
dataArray <- map["data"]
} else {
data <- map["data"]
}
code <- map["code"]
}
where:
var data: T?
var dataArray: [T]?
var code: Int = 0
The problem with this is that you need to check both data and dataArray for nil values.
You need to change your declaration of data to an array, since that's how it is in the JSON:
var data: [T]?
let apiResponse = Mapper<ResponseModel>().mapArray(JSONObject: response.result.value)
works for me
Anyone using SwiftyJSON and if you want an object from JSON directly without having a parent class, for example, you want the "data" from it. You can do something like this,
if let data = response.result.value {
let json = JSON(data)
let dataResponse = json["data"].object
let responseObject = Mapper<DataClassName>().mapArray(JSONObject: dataResponse)
}
This will return you [DataClassName]? as response.
Based on Abrahanfer's answer. I share my solution. I wrote a BaseResult for Alamofire.
class BaseResult<T: Mappable> : Mappable {
var Result : Bool = false
var Error : ErrorResult?
var Index : Int = 0
var Size : Int = 0
var Count : Int = 0
var Data : T?
var DataArray: [T]?
required init?(map: Map){
}
func mapping(map: Map) {
Result <- map["Result"]
Error <- map["Error"]
Index <- map["Index"]
Size <- map["Size"]
Count <- map["Count"]
if let _ = try? map.value("Data") as [T] {
DataArray <- map["Data"]
} else {
Data <- map["Data"]
}
}}
The usage for Alamofire :
WebService.shared.request(url, params, encoding: URLEncoding.default, success: { (response : BaseResult<TypeData>) in
if let arr = response.DataArray
{
for year in arr
{
self.years.append(year)
}
}
}, failure: {
})
The request method is :
func request<T: Mappable>(_ url: String,_ parameters: [String : Any] = [:], _ method: HTTPMethod = .post,_ httpHeaders: HTTPHeaders? = nil, encoding: ParameterEncoding = JSONEncoding.default, success: #escaping (T) -> Void, failure: #escaping () -> () ) {
AF.request(newUrl, method:method, parameters:parameters, encoding:encoding, headers: httpHeaders)
.responseJSON { response in
if let res = response.value {
let json = res as! [String: Any]
if let object = Mapper<T>().map(JSON: json) {
success(object)
return
}
}else if let _ = response.error {
failure()
}
}
}
And TypeData class is :
class TypeData : Mappable
{
var Id : String = ""
var Title: String = ""
required init(map: Map){
}
func mapping(map: Map) {
Id <- map["ID"]
Title <- map["YEAR"]
}}

Generic function to map JSON objects with ObjectMapper

I have a generic function:
func toObjectMapper<T: Mappable>(mapper: T, success: (result: Mappable) -> Void, failure: (error: NSError) -> Void){
let alomofireApiRequest = AlamofireApiRequest(apiRequest: self)
Alamofire.request(alomofireApiRequest)
.responseObject { (response: Response<T, NSError>) in
guard let value = response.result.value else {
failure(error: response.result.error!)
return
}
success(result: value)
}
}
And I want to call it like this:
public func login(login: String, password: String) -> UserResponse {
let params = ["email":login, "password":password]
let request = ApiRequest(method: .POST, path: "login", parameters: params)
request.toObjectMapper(UserResponse.self, success: { result in
print(result)
}, failure: { error in
print(error.description)
})
}
But I always get this error:
Cannot invoke 'toObjectMapper' with an argument list of type '(UserResponse.Type, success: (result: Mappable) -> Void, failure: (error: NSError) -> Void)'
This is my userResponse:
import Foundation
import ObjectMapper
import RealmSwift
public class UserResponse: Object, Mappable {
dynamic var id = 0
dynamic var name = ""
dynamic var address = ""
dynamic var zipcode = ""
dynamic var city = ""
dynamic var country = ""
dynamic var vat = ""
dynamic var email = ""
dynamic var created_at = NSDate()
dynamic var updated_at = NSDate()
override public static func primaryKey() -> String? {
return "id"
}
//Impl. of Mappable protocol
required convenience public init?(_ map: Map) {
self.init()
}
public func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
address <- map["address"]
zipcode <- map["zipcode"]
city <- map["city"]
country <- map["country"]
vat <- map["vat"]
email <- map["email"]
created_at <- map["created_at"]
updated_at <- map["updated_at"]
}
}
Any help ?
I think the problem is that you are trying to use UserResponse as an instantiated object but using UserResponse.self is only the class type.
A solution is to make UserResonse a singleton (or just instantiate an instance before passing it to 'toObjectMapper' as an argument)
I don't know if this code specifically will work but it's along these lines:-
public class UserResponse: Object, Mappable {
dynamic var id = 0
dynamic var name = ""
dynamic var address = ""
dynamic var zipcode = ""
dynamic var city = ""
dynamic var country = ""
dynamic var vat = ""
dynamic var email = ""
dynamic var created_at = NSDate()
dynamic var updated_at = NSDate()
static let shared = UserResponse() //singleton instantiation
override public static func primaryKey() -> String? {
return "id"
}
//Impl. of Mappable protocol
required convenience public init?(_ map: Map) {
self.init()
}
public func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
address <- map["address"]
zipcode <- map["zipcode"]
city <- map["city"]
country <- map["country"]
vat <- map["vat"]
email <- map["email"]
created_at <- map["created_at"]
updated_at <- map["updated_at"]
}
}
and then in your function call
public func login(login: String, password: String) -> UserResponse {
let params = ["email":login, "password":password]
let request = ApiRequest(method: .POST, path: "login", parameters: params)
request.toObjectMapper(UserResponse.shared, success: { result in
print(result)
}, failure: { error in
print(error.description)
})
}

Using Alamofire and Objectmapper the integer value always zero

I am using Alamofire with ObjectMapper and my model class is like that
class Category: Object, Mappable {
dynamic var id: Int = 0
dynamic var name = ""
dynamic var thumbnail = ""
var children = List<Category>()
override static func primaryKey() -> String? {
return "id"
}
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
thumbnail <- map["thumbnail"]
children <- map["children"]
}
}
and I am using Alamofire like that
Alamofire.request(.GET, url).responseArray { (response: Response<[Category], NSError>) in
let categories = response.result.value
if let categories = categories {
for category in categories {
print(category.id)
print(category.name)
}
}
}
the id is always zero, I don't know why?
I fixed it by adding transformation in the mapping function in model class like that
id <- (map["id"], TransformOf<Int, String>(fromJSON: { Int($0!) }, toJSON: { $0.map { String($0) } }))
thanks to #BobWakefield
Does the "id" field exist in the JSON file? If it does not, your initial value of zero will remain. Is the value in quotes in the JSON file? If it is, then it's a string. I don't know if ObjectMapper will convert it to Int.
Moved my comment to an answer.