如何使用 swift 在对象映射器中映射可变数量的嵌套对象

问题描述 投票:0回答:4

我有一个 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"
        }
    }
}
]

为了在我的代码中解析它,我创建了这个模型。我无法解析这些组,因为这不是一个列表,而是一个对象 -

    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"]
        }
    }    

我知道这是一个糟糕的 JSON。这些组应该位于列表中,而不是嵌套对象,但不幸的是,我收到了此响应。

这里在分组响应中,嵌套对象的数量是动态的,嵌套对象的key也是动态的。因此我无法将其解析为朋友属性。

所以我的问题是,如何映射“组”?

ios swift objectmapper
4个回答
1
投票

在映射组之前,我们需要一个可以将每个组与其键一起保存的类(即 xct)

例如

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

然后在你的 Person 类中你可以将其映射为 -

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

}

不要忘记从映射中调用此方法 -

override public func mapping(map: ObjectMapper.Map) {
...
groups = mapGroupsItems(map: map)
}

1
投票

尝试这种方法,使用自定义的

init(from decoder: Decoder)
作为
Groups
,对我来说效果很好。对非 SwiftUI 系统使用类似的方法。

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

0
投票

您的模型类结构将是

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

0
投票

感谢@shakif_ 的富有洞察力的回答。这是我根据该答案解决此问题的方法 -

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?
}
© www.soinside.com 2019 - 2024. All rights reserved.