Search code examples
iosswiftobjectmapper

iOS ObjectMapper incremental updating


I have a problem with my project, when my App starts, the configuration is automatically updated from the server, the json from server like this:

{
"version":1 
"config": 
{
    "key1": {xxx}
    "key2": {xxx}
    "key3": {xxx}
    "key4": {xxx}
    "key5": {xxx}
    "key6": {xxx}
}

And I use the ObjectMapper to convert json to model, like this:

struct GlobalConfig: Mappable {

var version = 0
var key1: [String: [LocalizedText]]?
var key2: [RouteObject]?
var key3: UrlConfig?
var key4: [String: [String: [[String: Any]]]]?
var key5: DegreeInfoList?
var key6: [String: String]?

init?(map: Map) { }

mutating func mapping(map: Map) {
    version <- map["version"]
    key1 <- map["key1"]
    key2 <- map["key2"]
    key3 <- map["key3"]
    key4 <- map["key4"]
    key5 <- map["key5"]
    key6 <- map["key6"]
}

Now there is a problem, when the json from server is large, it will consume a lot of traffic. In fact, some configurations do not need to be updated. So the json from server will like this:

{
"version":2
"config": 
{
    "key1": {xxx}
    "key2": {xxx}
}

It means key1 and key2 will be updated, key3, key4, key5 and key6 keep the old values. How to handle the JSON?


Solution

  • I don't understand what's the Mapper object is. It can be done with Codable.

    You can do like this to update your local config with incoming config.

    
    struct GlobalConfig: Codable {
        var version = 0
        var key1: [String: [LocalizedText]]?
        var key2: [RouteObject]?
        var key3: UrlConfig?
        var key4: [String: [String: [[String: Any]]]]?
        var key5: DegreeInfoList?
        var key6: [String: String]?
        
        mutating func combine(with config: GlobalConfig) {
            if let k = config.key1 { key1 = k }
            if let k = config.key2 { key2 = k }
            ...
        }
    }
    
    class App {
        var config: GlobalConfig
        
        func receiveConfig(newConfig: GlobalConfig) {
            config.combine(with: newConfig)
        }
    }
    
    

    Edited :

    In response to comment, here a possible solution for 'deep' combine.

    Very roughly :

    
    extension Dictionary {
        func combine(with dictionary: Dictionary) {
    
        } 
    }
    
    extension Array {
        func combine(with array: Array) {
    
        } 
    }
    
    protocol Combinable: Codable {
        func combine(with object: Self)
    }
    
    struct GlobalConfig: Combinable {
        var version = 0
        var key1: [String: [LocalizedText]]?
        var key2: [RouteObject]?
    
        mutating func combine(with config: GlobalConfig) {
            if let k = config.key1 { key1.combine(with: k) }
            if let k = config.key2 { key2.combine(with: k) }
            ...
        }
    }
    

    This is another subject, but types like [String: [String: [[String: Any]]]] should be avoided. It is hard to read, debug, and combine ;)

    I think it is better to use structured sub-models. Much safer, scalable and 'documentable'.

    
    
    struct GlobalConfig: Codable {
        ...
        // Old format: var key4: [String: [String: [[String: Any]]]]?
        var key4: MegaSettings?
        ...
    }
    
    struct MegaSettings: Combinable {
        struct ScreenOptions: Combinable {
            var options: [String: Any]
        }
    
        struct DisplayOptions: Combinable {
            struct DeviceDisplayOptions {
                var screen: [ScreenOptions]
            }
    
            var optionsPerDevice: [String: DeviceDisplayOptions]
        }
    
        var displayOptions: [String: DisplayOptions]
    }