My old JSON structure was like this:
{
"parameters": {
"customerId": 9,
"from": "2014-06-05T14:00:00",
"until": "2014-06-05T15:00:00",
"km": 20,
"insurance": false
},
"estimatesPerCategory": {
"2": {
"timeCost": 5,
"kmCost": 6,
...
}
To parse JSON I use ObjectMapper.
This code works very well:
if let myObject = Mapper<CostEstimateResult>().map(JSONObject: JSON) {
}
The class CostEstimateResult looks like this.
class CostEstimateResult : NSObject, Mappable {
var parameters:CostEstimateParameters?
var estimatesPerCategory:[String: CostEstimate]? // CostEstimate.id -> CostEstimate
override init() {}
required convenience init?(map: Map) {
self.init()
self.mapping(map: map)
}
func mapping(map: Map) {
estimatesPerCategory <- map["estimatesPerCategory"]
parameters <- map["parameters"]
}
}
And last but not least my CostEstimate class for estimatesPerCategory
class CostEstimate : NSObject, Mappable {
var timeCost:NSNumber?
...
override init() {}
required convenience init?(map: Map) {
self.init()
self.mapping(map: map)
}
func mapping(map: Map) {
timeCost <- map["timeCost"]
...
}
}
This is working. The mapper in CostEstimateResult will call the mapper in CostEstimates and I get my parsed data.
Now I changed the JSON Structure:
{
"parameters": {
//same like before
},
"estimatesPerCategory": {
"2": {
"total": {
"preTax": {
"value": 1,
"text": "1,00 €"
},
"configTax": false
},
"3": {
//same...
}
}
}
In my opinion in the basic structure nothing changes. estimatesPerCategory is still the same dictionary. The data behind estimatesPerCategory are changed, okay.
The problem is, when I call the mapper ...Mapper<CostEstimateResult>().map... the parameters will be parse, but not the costEstimates. The func mapping in CostEstimate isn't call anymore.
Long story short: It seems that the type of estimatesPerCategory in CostEstimateResult changes with the json structure. I can't figure out which type I need.
Now with new JSON Structure, your Class CostEstimate structure will be changed and a new Class PreTax will be required to be created:
class CostEstimateResult : NSObject, Mappable {
var parameters:CostEstimateParameters?
var estimatesPerCategory:[String: CostEstimate]? // CostEstimate.id -> CostEstimate
override init() {}
required convenience init?(map: Map) {
self.init()
self.mapping(map: map)
}
func mapping(map: Map) {
estimatesPerCategory <- map["estimatesPerCategory"]
}
}
class CostEstimate : NSObject, Mappable {
var total:totalTax?
override init() {}
required convenience init?(map: Map) {
self.init()
self.mapping(map: map)
}
func mapping(map: Map) {
total <- map["total"]
}
}
class totalTax : NSObject, Mappable {
var preTax:PreTax?
var configTax:Bool?
override init() {}
required convenience init?(map: Map) {
self.init()
self.mapping(map: map)
}
func mapping(map: Map) {
preTax <- map["preTax"]
configTax <- map["configTax"]
}
}
class PreTax : NSObject, Mappable {
var value:NSNumber?
var text:String?
override init() {}
required convenience init?(map: Map) {
self.init()
self.mapping(map: map)
}
func mapping(map: Map) {
value <- map["value"]
text <- map["text"]
}
}
Related
trying to convert ObjectMapper syntax to Swift 3.0:
class CustomJsonResponse: Mappable {
var status: String?
var response: String?
var errorCode: CustomErrorCode?
init() {
}
required init?(map: Map) {
}
func mapping(map: Map) {
status <- map["status"]
response <- map["response"]
errorCode <- (map["error_code"], CustomErrorCodeTransform())
}
}
class CustomChallengesResponse: CustomJsonResponse {
var challenges: [CustomChallenge]?
required init?(_ map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
challenges <- map["data.questions"]
}
}
I am getting an error at:
required init?(_ map: Map) {
super.init(map: map)
}
"Required intializer must be provided by subclass of CustomJsonResponse"
What am I doing wrong here? Any pointers on this would be great. Thanks!
I think you are missing:
init() {
super.init()
}
using the following simplified structure:
class Property: Mappable {
var path: String?
override func mapping(map: Map) {
path <- map["path"]
}
}
class Specification {
enum Name: String {
case Small = "SMALL"
case Medium = "MEDIUM"
}
}
class ItemWithImages: Mappable {
var properties: [Specification.Name : Property]?
override func mapping(map: Map) {
properties <- (map["properties"], EnumTransform<Specification.Name>())
}
}
... with that JSON:
[{"properties: ["SMALL": {"path": "http://..."}, "MEDIUM": {"path": "http://..."}]}]
... produces when using EnumTransform() as Transform the following (reasonable) compile error:
Binary operator '<-' cannot be applied to operands of type '[Specification.Name : Property]?' and '(Map, EnumTransform<Specification.Name>)'
So how does a custom TransformType have to look like, to map that dictionary the right way?
You can find the source of EnumTransform here: https://github.com/Hearst-DD/ObjectMapper/blob/master/ObjectMapper/Transforms/EnumTransform.swift
Thanks!
TransformTypes are IMHO primary designed to transform values and not keys. And your example is a little bit complicated because even value is not just basic type.
What do you think about this little hack?
struct ItemWithImages: Mappable {
var properties: [Specification.Name : Property]?
init?(_ map: Map) {
}
mutating func mapping(map: Map) {
let stringProperties: [String: Property]?
// map local variable
stringProperties <- map["properties"]
// post process local variable
if let stringProperties = stringProperties {
properties = [:]
for (key, value) in stringProperties {
if let name = Specification.Name(rawValue: key) {
properties?[name] = value
}
}
}
}
}
We should use DictionaryTransform instead of EnumTransform. We are transforming type of Dictionary [String:Any] to [Key:Value]. In our case type is [Specification.Name: Property].
Key need to conforms protocols like Hashable, RawRepresentable, and Key.RawValue should be String.
And Value Should be conforms Mappable Protocol.
class Property: Mappable {
var path: String?
override func mapping(map: Map) {
path <- map["path"]
}
}
class Specification {
enum Name: String {
case Small = "SMALL"
case Medium = "MEDIUM"
}
}
class ItemWithImages: Mappable {
var properties: [Specification.Name : Property]?
override func mapping(map: Map) {
properties <- (map["properties"], DictionaryTransform<Specification.Name,Property>())
}
}
I am getting this JSON in response from a webservice. I am able to understand the structure of the file.
{"response":200,"message":"fetch successfully","data":[
{"id":1,"question":"Are you currently exercising regularly?","choices":{"too_lazy":"Too lazy","times_1_3_week":"1-3 times a week","i_love_it":"I just love it!"},"answer_select":"single"},
{"id":2,"question":"Are you active member of Gym?","choices":{"yes":"Yes","no":"No"},"answer_select":"single"}]
}
and this is what I have so far
import Foundation
import UIKit
import ObjectMapper
class YASUserQuestion: Mappable {
var question: String
var id: Int
var choices: [YASExercisingQuestionChoices]
required init?(_ map: Map) {
question = ""
id = 0
choices = []
}
func mapping(map: Map) {
question <- map["question"]
id <- map["id"]
choices <- map["choices"]
}
}
class YASExercisingQuestionChoices: Mappable {
var tooLazy: String
var times_1_3_Week: String
var ILoveIt: String
required init?(_ map: Map) {
tooLazy = ""
times_1_3_Week = ""
ILoveIt = ""
}
func mapping(map: Map) {
tooLazy <- map["too_lazy"]
times_1_3_Week <- map["times_1_3_week"]
ILoveIt <- map["i_love_it"]
}
}
class YASGymMemberQuestionChoices: Mappable {
var yes: String
var no: String
required init?(_ map: Map) {
yes = ""
no = ""
}
func mapping(map: Map) {
yes <- map["yes"]
no <- map["no"]
}
}
I am not sure how to map this JSON response so can you please guide me How can I map this JSON ???
This will be your classes structure. After making these classes, next you just have map your JSON using ObjectMapper.
import UIKit
import ObjectMapper
class UserResponse: NSObject,Mappable {
var message: String?
var response: String?
var data: [DataClass]?
override init() {
super.init()
}
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
message <- map["message"]
response <- map["response"]
data <- map["data"]
}
}
class DataClass: NSObject,Mappable {
var answer_select: String?
var ID: String?
var question: String?
var choices: [ChoicesClass]?
override init() {
super.init()
}
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
answer_select <- map["answer_select"]
ID <- map["id"]
question <- map["question"]
choices <- map["choices"]
}
}
class ChoicesClass: NSObject,Mappable {
var i_love_it: String?
var times_1_3_week: String?
var too_lazy: String?
override init() {
super.init()
}
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
i_love_it <- map["i_love_it"]
times_1_3_week <- map["times_1_3_week"]
too_lazy <- map["too_lazy"]
}
}
I have this JSON:
{
"location": {
"position": {
"type": "Point",
"coordinates": [
45.579553,
11.751805
]
}
}
}
Which belongs to another JSON object.
Trying to map it with Realm and ObjectMapper, I am findind difficulties mapping the coordinates property which is an array of double.
That's what reading the documentation and S.O. seems to have sense:
import Foundation
import RealmSwift
import ObjectMapper
class Coordinate:Object, Mappable{
dynamic var latitude:Double = 0.0
dynamic var longitude:Double = 0.0
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
latitude <- map[""]
longitude <- map[""]
}
}
class Position: Object, Mappable{
var type:String = ""
var coordinates:Coordinate?
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
type <- map["type"]
coordinates <- map["coordinates"]
}
}
class Location: Object, Mappable{
dynamic var id = ""
dynamic var position:Position?
dynamic var desc = ""
override static func indexedProperties()->[String]{
return["id"]
}
override class func primaryKey() -> String? {
return "id"
}
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
id <- map["id"]
position <- map["position"]
}
}
However I'm stuck in understanding how to map the "coordinates" object. Please note that this problem has nothing to do with ObjectMapper itself, it's more of a question on how to assign an array of Double to a property in a Realm model.
I was able to solve this following the indications in this issue:
https://github.com/realm/realm-cocoa/issues/1120 (credits #jazz-mobility)
class DoubleObject:Object{
dynamic var value:Double = 0.0
}
class Position: Object, Mappable{
var type:String = ""
var coordinates = List<DoubleObject>()
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
type <- map["type"]
var coordinates:[Double]? = nil
coordinates <- map["coordinates"]
coordinates?.forEach { coordinate in
let c = DoubleObject()
c.value = coordinate
self.coordinates.append(c)
}
}
}
You can now simply use List<Double>() without it storing an object.
More information can be found here: https://academy.realm.io/posts/realm-list-new-superpowers-array-primitives/
#objcMembers class RealmObject: Object, Mappable {
dynamic var listValues = List<MyRealmObject>()
required convenience init?(map: Map) {
self.init()
}
// Mappable
func mapping(map: Map) {
listValues <- (map["listValues"], RealmlistObjectTransform())
}
}
#objcMembers class MyRealmObject: Object, Mappable {
required convenience init?(map: Map) {
self.init()
}
// Mappable
func mapping(map: Map) {
}
}
class RealmlistObjectTransform: TransformType {
typealias Object = List<MyRealmObject> // My Realm Object here
typealias JSON = [[String: Any]] // Dictionary here
func transformFromJSON(_ value: Any?) -> List<MyRealmObject>? {
let list = List<MyRealmObject>()
if let actors = value as? [[String: Any]] {
let objects = Array<MyRealmObject>(JSONArray: actors)
list.append(objectsIn: objects)
}
return list
}
func transformToJSON(_ value: List<MyRealmObject>?) -> [[String: Any]]? {
if let actors = value?.sorted(byKeyPath: "").toArray(ofType: MyRealmObject.self).toJSON() {
return actors
}
return nil
}
}
I'm receiving the following json from the API:
[ { "first_id": 1,
"second_objs": [ { "second_id": 2,
"third_objs": [ { "third_id": 3,
"param": "abcd" }] } ] } ]
And serialising it using ObjectMapper:
class First: NSObject, Mappable {
var first_id: Int?
var second_objs: [Second]?
required init?(_ map: Map){}
func mapping(map: Map) {
first_id <- map["first_id"]
second_objs <- map["second_objs"]
}
}
class Second: NSObject, Mappable {
var second_id: Int?
var third_objs: [Third]?
required init?(_ map: Map){}
func mapping(map: Map) {
second_id <- map["second_id"]
third_objs <- map["third_objs"]
}
}
class Third: NSObject, Mappable {
var third_id: Int?
var param: String?
required init?(_ map: Map){}
func mapping(map: Map) {
third_id <- map["third_id"]
param <- map["param"]
}
}
Now after recieving and modifying that JSON I need to send it back to the API.
However before sending back I need to rename keys "second_objs" and "third_objs" to "something_else_2" and "something_else_3".
[ { "first_id": 1,
"something_else_2": [ { "second_id": 2,
"something_else_3": [ { "third_id": 3,
"param": "abcd" }] } ] } ]
How do I do the renaming in a clean way?