skip to Main Content

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

4

Answers


  1. Chosen as BEST ANSWER

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

  2. 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
    }
    
    Login or Signup to reply.
  3. 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)
            }
        }
        
    }
    
    Login or Signup to reply.
  4. 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)
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search