Home > front end >  How to map variable number of nested objects in object mapper swift
How to map variable number of nested objects in object mapper swift

Time:11-01

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"?

CodePudding user response:

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)
}

CodePudding user response:

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)
        }
    }
    
}

CodePudding user response:

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
}

CodePudding user response:

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?
}
  • Related