Swift - Mapping of Nested Objects (Objectmapper) - json

I am developing an application with Swift 4. Where I make a call to the APIRest with Alamofire and I want to map the JSON response with Objectmapper. Well, the JSON that calls me back is the following:
The code to the APIRest is:
func retrievePostListData() {
Alamofire
.request("http://www.speedrun.com/api/v1/games", method: .get)
.validate()
.responseArray(completionHandler: { (response:
DataResponse<[PostModelSpeedRunModel]>) in
switch response.result {
case .success(let posts):
self.remoteRequestHandler?.onPostsRetrievedData(posts)
case .failure( _):
self.remoteRequestHandler?.onError()
}
})
}
The problem is that I do not know how to access each of the values (func mapping). Because there are some nested values. In addition to that some of the annunciations are objects and others are array. My erroneous code is the following:
import Foundation
import ObjectMapper
struct PostModelSpeedRunModel {
var id = ""
var international = ""
var abbreviation = ""
var links = [Links]??? // I need to get "rel" and "uri" of "runs"
var uri = ""
}
extension PostModelSpeedRunModel: Mappable {
init?(map: Map) {
}
mutating func mapping(map: Map) {
id <- map["data.id"]
international <- map["data.international"]
abbreviation <- map["data.abbreviation"]
link <- map["data.Links"]
uri <- map["data.logo"]
}
}
Can you help me do / understand doing the function mapping? Thanks

I'm assuming you are getting no values at all, because I tried your code and got nothing. If you only need the contents of the data array from the json and, as you have it now, ObjectMapper is expecting a json with just an array of PostModelSpeedRunModels. Therefore, you need to add a keyPath to tell AlamofireObjectMapper it's starting point:
Alamofire.request("http://www.speedrun.com/api/v1/games", method: .get)
.responseArray(keyPath: "data") { (response: DataResponse<[PostModelSpeedRunModel]>) in
...
}
If you also need the info from the pagination node, then you'll need to create a new object that has a data and pagination properties, and change responseArray(keyPath: ...) to simply responseObject using the new root object in DataResponse.
Then I believe you only want runs's uri, so I recommend just having a String in your model for storing it, instead of an array of Links. Assuming that the array of links is unsorted and may change order in the future (if not you can access directly like map["links.1.uri"] and you are done), all links need to be parsed and then filtered. It can be done as follows:
struct PostModelSpeedRunModel {
var id = ""
var international = ""
var abbreviation = ""
var runsLink = ""
var uri = ""
}
extension PostModelSpeedRunModel: Mappable {
init?(map: Map) {
}
mutating func mapping(map: Map) {
id <- map["id"]
international <- map["international"]
abbreviation <- map["abbreviation"]
uri <- map["logo"]
var links: [Link]?
links <- map["links"]
if let uri = links?.first(where: {$0.rel == "runs"})?.uri {
runsLink = uri
}
}
}
struct Link {
var rel = ""
var uri = ""
}
extension Link: Mappable {
init?(map: Map) {
}
mutating func mapping(map: Map) {
rel <- map["rel"]
uri <- map["uri"]
}
}

Related

Parsing JSON & work with ObjectMapper,SwiftRealm,ObjectMapperAdditions,ObjectMapperAdditionsRealm & Alamofire

regards, I am trying to serialize a json from the placeHolder page, the users one (https://jsonplaceholder.typicode.com/ users), I am trying to use the following libraries together:
-> Alamofire
-> RealmSwift
-> ObjectMapper
-> ObjectMapperAdditions
-> ObjectMapper_Realm
Here is the model that I am defining, but the problem is that SwiftRealm does not support the ArrayDictionary data type, as you try changing the type in the COMPANY & ADDRESS attributes, which are of the array type, try to put it as List , [Address1]?, Address1, List , etc. but it does not work, it always marks the same error "The operation could not be completed, ObjectMapper could not serialize the response.", so it can be received as an object but it can not be saved in RealmSwift or failing that What to do, but you will also receive as JSON or ARRAY and it is also not possible to serialize it, have you implemented something like that?
import Foundation
import RealmSwift
import ObjectMapper
import ObjectMapperAdditions
import ObjectMapper_Realm
class PlaceHolderClass: Object, Mappable {
#objc dynamic var id = 0
#objc dynamic var name = ""
#objc dynamic var username = ""
#objc dynamic var email = ""
var address: Address1?
#objc dynamic var phone = ""
#objc dynamic var website = ""
var company: Company?
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
username <- map["username"]
email <- map["email"]
address <- map["address"]
phone <- map["phone"]
website <- map["website"]
company <- map["company"]
}
}
class Address1: Object, Mappable {
var street = List<String>()
var suite = List<String>()
var city = List<String>()
var zipcode = List<String>()
var geo: Geo?
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
street <- map["street"]
suite <- map["suite"]
city <- map["city"]
zipcode <- map["zipcode"]
geo <- map["geo"]
}
}
class Geo: Object, Mappable {
var lat = List<String>()
var lng = List<String>()
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
lat <- map["lat"]
lng <- map["lng"]
}
}
class Company: Object, Mappable {
var name = List<String>()
var catchPhrase = List<String>()
var bs = List<String>()
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
name <- map["name"]
catchPhrase <- map["catchPhrase"]
bs <- map["bs"]
}
}
My request Code:
func requestPlaceHolder(){
let URL = "https://jsonplaceholder.typicode.com/users"
Alamofire.request(URL).responseObject{ (response: DataResponse<PlaceHolderClass>) in
switch response.result {
case .success(let objects):
let realm = try! Realm()
let myPlaceHolderResponse = PlaceHolderClass(value: response.result.value)
try! realm.write {
realm.add(myPlaceHolderResponse, update: true)
print("se agrego correctamente")
}
print(Realm.Configuration.defaultConfiguration.fileURL)
case .failure(let error):
print("Ocurrio el siguiente error \(error.localizedDescription)")
}
print(Realm.Configuration.defaultConfiguration.fileURL)
}
}

How to convert array of mappables to JSON?

I using Objectmapper and Alamofire in my project.
Let's have a struct:
struct User: Mappable {
var name = ""
init?(map: Map) {}
mutating func mapping(map: Map) {
name <- map["name"]
}
}
and then i want to make a request to send array of users to server like this:
var users = [User]()
...
let parameters = ?//i want to convert users array to JSON
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters).responseJSON {
...
}
But i don't know how to convert users array to parameters for input to Alamofire request.
To convert an object to String using mapper:
let user = User()
let userString = Mapper<User>.toJSONString(user)
To convert it to JSON:
let userJSON = Mapper<User>().toJSON(user)
You can checkout various apis provided by ObjectMapper by command-clicking 'Mapper' in your code or go to Mapper.swift.

How to map dynamic properties in model class (Swift)

I am new to IOS Development, i came across a really interesting situation, i have this json response coming server side
{
"caps": {
"first_key": "34w34",
"first_char": "34w45",
"first_oddo": "34w34"
.... : .....
.... : .....
}
}
My issue this that keys inside "caps" object can be dynamic (like what if one more value is added). I am using ObjectMapper to mapper to map values from response to model class. I have this model class
class User: Mappable {
var first_key: String?
var first_char: String?
var first_oddo: String?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
first_key <- map["first_key"]
first_char <- map["first_char"]
first_oddo <- map["first_oddo"]
}
}
Now as i dont know how to populate my model if values in json response are changed (because that are dynamic). I hope i have explained it well. I think i dont want hard coded values inside model?
This solution uses the Codable protocol introduced with Swift 4.
If the keys of your JSON are dynamic then you need a Dictionary.
JSON
Given this JSON
let data = """
{
"caps": {
"first_key": "34w34",
"first_char": "34w45",
"first_oddo": "34w34"
}
}
""".data(using: .utf8)!
The Response Model
You can define a struct like this
struct Response:Codable {
let caps: [String:String]
}
Decoding 🎉🎉🎉
Now you can decode your JSON
if let response = try? JSONDecoder().decode(Response.self, from: data) {
print(response.caps)
}
Output
["first_key": "34w34", "first_oddo": "34w34", "first_char": "34w45"]

AlamofireObjectMapper in swift 3

I want to use AlamofireObjectMapper for the first time to parse a json response in swift.
The response is:
"success": true,
"terms": "https:\/\/currencylayer.com\/terms",
"privacy": "https:\/\/currencylayer.com\/privacy",
"timestamp": 1480007048,
"source": "USD",
"quotes": {
"USDAED": 3.672598,
"USDAFN": 66.599998,
"USDALL": 127.999937,
"USDAMD": 478.679993,
"USDANG": 1.780277,
"USDAOA": 165.072998,
"USDARS": 15.497261,
"USDAUD": 1.348899,
"USDAWG": 1.79,
"USDAZN": 1.714104,
"USDBAM": 1.855297,
"USDBBD": 2,
"USDBDT": 79.179735,
"USDBGN": 1.854199,
"USDBHD": 0.377036,
"USDBIF": 1668.300049,
"USDBMD": 1,
"USDBND": 1.429902,
"USDBOB": 6.870014,
"USDBRL": 3.396898,
"USDBSD": 1,
}
I mapped it like this:
class ModelCurrency: Mappable {
var success : Bool?
var terms : String?
var privacy : String?
var timestamp : CGFloat?
var source : String?
var quotes : [Quotes]?
init() {}
required init?(map: Map) {
}
func mapping(map: Map) {
success<-map["success"]
terms<-map["terms"]
privacy<-map["privacy"]
timestamp<-map["timestamp"]
source<-map["source"]
quotes<-map["quotes"]
print("It json\(terms)")
}
}
class Quotes : Mappable {
var name : String?
var val : CGFloat?
required init?(map: Map) {
}
func mapping(map: Map) {
name<-map["name"]
val<-map["val"]
}
}
And in my controller:
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
super.viewDidLoad()
let URL = "http://www.apilayer.net/api/live?access_key=ad847a0a855c0647590df2b818923025"
Alamofire.request(URL).responseArray(queue: "quotes") { (response: DataResponse<[Quotes]>) in
let arrayCurency = response.result.value!
for quotes in arrayCurency {
print(quotes.name!)
print(quotes.val!)
}
}
}
It gives me error mapping and this error:
cannot convert value 'String' to expected argument type 'DispatchQueue?'
There are several issues.
You want to reference DataResponse<ModelCurrency> rather than DataResponse<[Quotes]>.
You want to use responseObject, not responseArray.
You don't want to use that queue parameter with a String value. The queue parameter is used to specify which dispatch queue you want the completion handlers to run. But that's not what you're doing here, so you should just remove it.
The value associated with the quotes key is not an array of objects. It is yet another dictionary. So you should map it to a dictionary, and then use map method to convert that to an array of Quote objects.
So, pulling that all together:
Alamofire.request(urlString).responseObject { (response: DataResponse<ModelCurrency>) in
...
}
And
class ModelCurrency: Mappable {
var success : Bool?
var terms : String?
var privacy : String?
var timestamp : CGFloat?
var source : String?
var quotes : [Quote]?
required init?(map: Map) { }
func mapping(map: Map) {
success <- map["success"]
terms <- map["terms"]
privacy <- map["privacy"]
timestamp <- map["timestamp"]
source <- map["source"]
var dictionary: [String: CGFloat]?
dictionary <- map["quotes"]
quotes = dictionary?.map { return Quote(name: $0.key, val: $0.value) }
}
}
class Quote {
var name : String?
var val : CGFloat?
init(name: String?, val: CGFloat?) {
self.name = name
self.val = val
}
}
(I've renamed Quotes to Quote as it would appear to be a quote for a single currency.)
The issue is with this part of code:
func mapping(map: Map) {
name<-map["name"]
val<-map["val"]
}
in Quotes.
What you should do is map dictionary:
var quotes: [String : CGFloat] = [:]
and when mapping use:
quotes <- map["quotes"]
Take a look at:
https://github.com/Hearst-DD/ObjectMapper
at the basics there is mapping dictionary.
To be more specific, in quote object you don't have name and val JSON objects, it is just what you want. If you map it to dictionary you will be able to access theses values.
Haven't seen your image - but if you don't change above the app will crash.
For the above issue you need to provide queue in which you want to run your request, if you don't want to mess with that and you actually want keyPath, then you need to call function with keyPath: instead of queue:. Link:
https://github.com/tristanhimmelman/AlamofireObjectMapper
look for Array Responses

How to map a realm list of custom objects using Mappable protocol in Swift

In my Realm object model I have an object called "Event". Each Event has a list of EventLocatons. I am trying to map these object from json, but the list of EventLocations is always empty.
The objects look like this (simplified for clarity):
class Event: Object, Mappable {
override class func primaryKey() -> String? {
return "id"
}
dynamic var id = ""
var eventLocations:List<EventLocation> = List<EventLocation>()
func mapping(map: Map) {
id <- map["id"]
eventLocations <- map["eventLocations"]
}
}
class EventLocation: Object, Mappable {
override class func primaryKey() -> String? {
return "id"
}
dynamic var id: String = ""
dynamic var name: String = ""
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
}
}
The json, that I have is an array of Event objects. It comes from an Alamofire response and I map it like that:
var events = Mapper<Event>().mapArray(json!)
The json looks like this:
[
{
"id" : "21dedd6d",
"eventLocations" : [
{
"name" : "hh",
"id" : "e18df48a",
},
{
"name" : "tt",
"fileId" : "be6116e",
}
]
},
{
"id" : "e694c885",
"eventLocations" : [
{
"name" : "hh",
"id" : "e18df48a",
},
{
"name" : "tt",
"fileId" : "be6116e",
}
]
}
]
Does anyone know how can I map a list of custom objects using the Mappable protocol. Whay is the "eventLocations" list always empty?
Having a look at one of the Issues page on ObjectMapper's GitHub repo, it doesn't look like Realm List objects are properly supported yet.
That issue also lists a potential workaround for getting it to work for the time being, which I'll mirror here:
class MyObject: Object, Mappable {
let tags = List<Tag>()
required convenience init?(_ map: Map) { self.init() }
func mapping(map: Map) {
var tags: [Tag]?
tags <- map["tags"]
if let tags = tags {
for tag in tags {
self.tags.append(tag)
}
}
}
}
Another solution could be to implement a custom transform for ObjectMapper.
You can find an implementation here.
Then in your code:
eventLocations <- (map["eventLocations"], ListTransform<EventLocation>())
You can add an operator for this.
Swift 3 implementation:
import Foundation
import RealmSwift
import ObjectMapper
infix operator <-
/// Object of Realm's List type
public func <- <T: Mappable>(left: List<T>, right: Map) {
var array: [T]?
if right.mappingType == .toJSON {
array = Array(left)
}
array <- right
if right.mappingType == .fromJSON {
if let theArray = array {
left.append(objectsIn: theArray)
}
}
}
Now you don't need any additional code or transforms.
list <- map["name"]
I've created a gist. Please, check https://gist.github.com/danilValeev/ef29630b61eed510ca135034c444a98a