I started to use ObjectMapper this week and I'm trying to map a JSON into 2 CustomClasses but I don't known if ObjectMapper has some function to do what I want. First CustomClass has a property of type: [String:CustomClass2] where the index of this Dictionary should be property ID of second CustomObject.
JSON Used:
{
"types": [
{
"id": "mk8QPMSo2xvtSoP0cBUD",
"name": "type 1",
"img": "type_1",
"showCategories": false,
"modalityHint": [
"K7VqeFkRQNXoh2OBxgIf"
],
"categories": [
"mP3MqbJrO5Da1dVAPRvk",
"SlNezp2m3PECnTyqQMUV"
]
}
]
}
Classes Used:
class MyClass: Mappable {
var types:[String:MyClass2] = [String:MyClass2]() //Index should be ID property of MyClass2 Object
required init?(map:Map) {
guard map.JSON["types"] != nil else {
return nil
}
}
func mapping(map: Map) {
types <- map["types"]
}
}
class MyClass2: Mappable {
private var id: String!
private var name: String!
private var img: String!
private var showCategories: Bool!
private var modalityHint: [String]?
private var categories: [String]?
required init?(map: Map) { }
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
img <- map["img"]
showCategories <- map["showCategories"]
modalityHint <- map["modalityHint"]
categories <- map["categories"]
}
In your JSON the types key is an array not a Dictionary.
Change:
var types:[String:MyClass2] = [String:MyClass2]()
To:
var types:[Class2] = []
Like this:
class MyClass: Mappable {
private var arrayTypes = [MyClass2] {
didSet{
var mapTypes = [String:MyClass2]?
for obj in arrayTypes {
mapTypes[obj.id] = obj
}
types = mapTypes
}
}
var types:[String:MyClass2] = [String:MyClass2]()
required init?(map:Map) {
guard map.JSON["types"] != nil else {
return nil
}
}
func mapping(map: Map) {
arrayTypes <- map["types"]
}
}
Related
I have a JSON response (bellow) and I need to parse this -
[
{
"id":123,
"name":"Fahim Rahman",
"age":25,
"friends":[
{
"firstName": "Imtiaz",
"lastName": "Khan",
"avatar_url": null
}
],
"groups":{
"xcet":{
"name":"xcek cert etsh tnhg",
"createdDate":"2022-10-31T10:00:48Z"
},
"juyt":{
"name":"jfd uyt you to",
"createdDate":"2021-09-13T10:00:00Z"
},
"some random key":{
"name": "some name",
"createdDate":"2026-03-27T10:00:00Z"
}
}
}
]
To parse this in my code I've created this model. I can not able to parse the groups as that is not a list but an object -
import ObjectMapper
class Person: BaseObject {
#objc dynamic var ID: Int = -1
#objc dynamic var name: String = ""
#objc dynamic var age: Int = -1
var friendsList = List<Friends>()
override func mapping(map: ObjectMapper.Map) {
ID <- map["id"]
name <- map["name"]
age <- map["age"]
friendsList <- map["friends"]
}
}
class Friends: BaseObject {
#objc dynamic var firstName: String = ""
#objc dynamic var lastName: String = ""
#objc dynamic var avatarURL: String = ""
override func mapping(map: ObjectMapper.Map) {
firstName <- map["firstName"]
lastName <- map["name"]
avatarURL <- map["avatar_url"]
}
}
I know it's a bad JSON. The groups should be on the list instead of the nested objects but unfortunately, I'm getting this response.
Here in the response of groups, the number of nested objects is dynamic and the key of the nested object is also dynamic. Thus I can not able to parse this as friends attribute.
So my question is, how can I map the "groups"?
Before mapping groups, we need a class that can hold each Group alongside its key (i.e. xct)
For example
Class Groups: BaseObject {
#objc dynamic var key: String = ""
#objc dynamic var value: GroupsItem?
convenience init(key: String, value: GroupsItem) {
self.init()
self.key = key
self.value = value
}
}
Class GroupsItem: BaseObject {
#objc dynamic var name: String?
#objc dynamic var createdDate: String?
...
}
Then inside your Person class you can map this as -
private func mapGroupsItems(map: ObjectMapper.Map) -> List<GroupsItem> {
var rowsDictionary: [String: Groups]?
rowsDictionary <- map["groups"]
let rows = List<GroupsItem>()
if let dictionary = rowsDictionary {
for (key, value) in dictionary {
rows.append(GroupsItem(key: key, value: value))
}
}
return rows
}
dont forget to call this method from mapping -
override public func mapping(map: ObjectMapper.Map) {
...
groups = mapGroupsItems(map: map)
}
try this approach, using a custom init(from decoder: Decoder) for Groups, works well for me. Use a similar approach for non-SwiftUI systems.
struct ContentView: View {
#State var people: [Person] = []
var body: some View {
ForEach(people) { person in
Text(person.name)
ForEach(Array(person.groups.data.keys), id: \.self) { key in
Text(key).foregroundColor(.red)
Text(person.groups.data[key]?.name ?? "no name").foregroundColor(.blue)
Text(person.groups.data[key]?.createdDate ?? "no date").foregroundColor(.blue)
}
}
.onAppear {
let json = """
[
{
"id":123,
"name":"Fahim Rahman",
"age":25,
"friends":[
{
"firstName": "Imtiaz",
"lastName": "Khan",
"avatar_url": null
}
],
"groups":{
"xcet":{
"name":"xcek cert etsh tnhg",
"createdDate":"2022-10-31T10:00:48Z"
},
"juyt":{
"name":"jfd uyt you to",
"createdDate":"2021-09-13T10:00:00Z"
},
"some random key":{
"name": "some name",
"createdDate":"2026-03-27T10:00:00Z"
}
}
}
]
"""
if let data = json.data(using: .utf8) {
do {
self.people = try JSONDecoder().decode([Person].self, from: data)
print("---> people: \(people)")
} catch {
print("decode error: \(error)")
}
}
}
}
}
struct Person: Identifiable, Codable {
let id: Int
var name: String
var age: Int
var friends: [Friend]
var groups: Groups
}
struct Friend: Codable {
var firstName, lastName: String
var avatarURL: String?
enum CodingKeys: String, CodingKey {
case firstName, lastName
case avatarURL = "avatar_url"
}
}
struct Info: Codable {
var name: String
var createdDate: String
}
struct Groups: Identifiable, Codable {
let id = UUID()
var data: [String:Info] = [:]
init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
self.data = try container.decode([String:Info].self)
} catch {
print(error)
}
}
}
Your Model classes structure will be
// MARK: - Welcome7Element
struct Welcome7Element {
let id: Int
let name: String
let age: Int
let friends: [Friend]
let groups: Groups
}
// MARK: - Friend
struct Friend {
let firstName, lastName: String
let avatarURL: NSNull
}
// MARK: - Groups
struct Groups {
let xcet, juyt, someRandomKey: Juyt
}
// MARK: - Juyt
struct Juyt {
let name: String
let createdDate: Date
}
Thank you #shakif_ for your insightful answer. Here is how I solved this based on that answer -
import ObjectMapper
import RealmSwift
class Person: BaseObject {
#objc dynamic var ID: Int = -1
#objc dynamic var name: String = ""
#objc dynamic var age: Int = -1
var friendsList = List<Friends>()
var groups = List<Group>
override func mapping(map: ObjectMapper.Map) {
ID <- map["id"]
name <- map["name"]
age <- map["age"]
friendsList <- map["friends"]
groups = extractGroups(map)
}
private func extractGroups(_ map: ObjectMapper.Map) -> List<Group> {
let items = List<Group>()
var modifiedJSON = [String: Group]
modifiedJSON <- map["groups"]
for (key,value) in modifiedJSON {
let item = GroupMapper(key: key, value: value)
if let group = item.value {
items.append(group)
}
}
return items
}
}
class Friends: BaseObject {
#objc dynamic var firstName: String = ""
#objc dynamic var lastName: String = ""
#objc dynamic var avatarURL: String = ""
override func mapping(map: ObjectMapper.Map) {
firstName <- map["firstName"]
lastName <- map["name"]
avatarURL <- map["avatar_url"]
}
}
class Group: BaseObject {
#objc dynamic var name: String = ""
#objc dynamic var createdDate: String = ""
override func mapping(map: ObjectMapper.Map) {
name <- map["name"]
createdDate <- map["createdDate"]
}
}
struct GroupMapper {
var key: String = ""
var value: Group?
}
I have the following JSON, which I am using together with ObjectMapper:
Open Api
Response snippet
{
"data": [
{
"CategoryName": "רוגע",
"CategoryID": "63",
"CategoryDate": "2016-08-26 02:12:05",
"CategoryImage": "relax.png",
"SubCategoryArray": [
{
"SubCategoryName": "רוגע",
"SubCategoryRefID": "63",
"SubCategoryID": "86",
"SubCategoryDate": "2016-08-28 02:57:07",
"TextArray": [
{
"TextID": "32",
"Text": "<p dir=\"rtl\"><span style=\"font-size:48px\"><strong><span dir=\"RTL\" lang=\"HE\" style=\"font-family:Arial\">פרופורציה</span></strong> . הכול הבל הבלים. חולף כהרף עין. אז לנשום.</span></p>\r\n"
},
My problem is getting the data from "SubCategoryArray", and "TextArray"
I tried to do the following in my mapping:
import UIKit
import ObjectMapper
class APIResult: Mappable {
var data : [dataArray]?
required init?(map: Map){
}
func mapping(map: Map) {
data <- map["data"]
}
}
class dataArray: Mappable{
var CategoryName: String?
var CategoryID: String?
var CategoryDate: String?
var CategoryImage: String?
var SubCategoryArray: SubCategoryArray?
required init?(map: Map){
}
func mapping(map: Map) {
CategoryName <- map["CategoryName"]
CategoryID <- map["CategoryID"]
CategoryDate <- map["CategoryDate"]
CategoryImage <- map["CategoryImage"]
SubCategoryArray <- map["SubCategoryArray"]
}
}
class SubCategoryArray: Mappable {
var SubCategoryName: String?
var SubCategoryRefID: String?
var SubCategoryID: String?
var SubCategoryDate: String?
var TextArray: TextArray?
required init?(map: Map){
}
func mapping(map: Map) {
SubCategoryName <- map["SubCategoryName"]
SubCategoryRefID <- map["SubCategoryRefID"]
SubCategoryID <- map["SubCategoryID"]
SubCategoryDate <- map["SubCategoryDate"]
TextArray <- map["TextArray"]
}
}
class TextArray: Mappable {
var TextID: String?
var Text:String?
required init?(map: Map){
}
func mapping(map: Map) {
TextID <- map["TextID"]
Text <- map["Text"]
// SubCategoryID <- map["SubCategoryID"]
// SubCategoryDate <- map["SubCategoryDate"]
// TextArray <- map["TextArray"]
}
}
Please point what I am doing wrong.
This is how you would map this data
import Foundation
import ObjectMapper
class customData: Mappable {
var categoryName: String = ""
var categoryId: String = ""
var subCategoryArray: [SubCategoryArray] = []
required init?(_ map: Map) {
}
func mapping(map: Map) {
categoryName <- map["data.CategoryName"]
categoryId <- map["data.CategoryID"]
subCategoryArray <- map["data.SubCategoryArray"]
}
}
class SubCategoryArray: Mappable {
var SubCategoryName: String = ""
var SubCategoryRefID: String = ""
var textArray: [TextArray] = []
required init?(_ map: Map) {
}
func mapping(map: Map) {
SubCategoryName <- map["SubCategoryName"]
SubCategoryRefID <- map["SubCategoryRefID"]
textArray <- map["TextArray"]
}
}
class TextArray: Mappable {
var TextID: String = ""
var Text: String = ""
required init?(_ map: Map) {
}
func mapping(map: Map) {
TextID <- map["TextID"]
Text <- map["Text"]
}
}
Let me know if you find any difficulty.
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 currently using ObjectMapper for Swift for mapping JSON Object from API to model Object
but my restful api return API look like this:
{
success: true,
data:
[{
"stats":{
"numberOfYes":0,
"numberOfNo":2,
"progress":{
"done":false,
"absolute":"2/100",
"percent":2
}
},
"quickStats":null,
"uid":5,
"name":"Flora",
"imageArray":[
"http://s3.com/impr_5329beac79400000",
"http://s3.com/impr_5329beac79400001"
],
"metaData":{
"description":"Floral Midi Dress",
"price":"40$"
}
}]
}
In data node is array i can't look up to mapping with this code
let json = JSON(responseObject!)
for tests in json["impressions"][0] {
let test = Mapper<myTests>().map(tests)
println(test?.impressionID)
}
How should I fix?
Thanks
** Edited **
I found solution similar #tristan_him
ObjectModel mapping structure
class Response: Mappable {
var success: Bool?
var data: [Data]?
required init?(_ map: Map) {
mapping(map)
}
func mapping(map: Map) {
success <- map["success"]
data <- map["data"]
}
}
class Data: Mappable {
var uid: Int?
var name: String?
// add other field which you want to map
required init?(_ map: Map) {
mapping(map)
}
func mapping(map: Map) {
uid <- map["uid"]
name <- map["name"]
}
}
Mapping with Alamofire response
let response: Response = Mapper<Response>().map(responseObject)!
for item in response.data! {
let dataModel: Data = item
println(dataModel.name)
}
You can map the JSON above by using the following class structure:
class Response: Mappable {
var success: Bool?
var data: [Data]?
required init?(_ map: Map) {
mapping(map)
}
func mapping(map: Map) {
success <- map["success"]
data <- map["data"]
}
}
class Data: Mappable {
var uid: Int?
var name: String?
// add other field which you want to map
required init?(_ map: Map) {
mapping(map)
}
func mapping(map: Map) {
uid <- map["uid"]
name <- map["name"]
}
}
Then you can map it as follows:
let response = Mapper<Response>().map(responseObject)
if let id = response?.data?[0].uid {
println(id)
}