Developear
Software   Twitter   Contact   RSS  



In the vast majority of native client side software, it is common to create client-side model objects from a server representation (most commonly JSON).

This is the flow that see the most:
  1. Request an API endpoint and download JSON response
  2. Convert JSON to a Dictionary
  3. Initialize/update a model directly with the dictionary itself
The antipattern I see here, is number 3.

Here are the reasons I believe this to be a problem:
  1. Ties creation of your model to a specific dictionary format, and at some level the API endpoint (and clients have to know this fact AND the format)
  2. Creates ambiguity if the dictionary is not formatted correctly - for example, it could lead to certain parts of your model being valid and others that are not
  3. Introduces parsing logic into your models, which ideally should just store data, and contain as little of this kind of logic as possible
Here is an example of using a dictionary to initialize your model:

struct User {
    let name: String?
    let id: Int?

    init (dictionary: [String: Any]) {
        self.name = dictionary["name"] as? String
        self.id = dictionary["userID"] as? Int
    }
}


This now means you must have a dictionary (with a certain key/value type) to create a User. You also must make the properties optional unless you make the initializer failable (which comes with it's own issues) or force unwrap and force cast. All of this makes it harder to tell if your whole model is valid or not, like I mentioned before.

While this is a pretty simple example, and the parsing logic is relatively trivial - many of the cases in our applications are much more complicated.

In order to start to solve this coupling, the first thing we are going to do is remove the current initializer, change the current properties to not be optional, and add an initializer that consumes the data to fill in the properties:

struct User {
    let name: String
    let id: Int

    init (name: String, id: Int) {
        self.name = name
        self.id = id
    }
}


Now our model does no parsing, and is far more reusable. Now we must implement the parsing, for this example I will simply add an extension to User, with a factory method.

extension User {
    static func userWithDictionary(dictionary: [String: Any]) -> User? {
        if let name = dictionary["name"] as? String, id = dictionary["userID"] as? Int {
            return User(name: name, id: id)
        }
        return nil
    }
}


In conclusion, now our User model is totally decoupled from any dictionary format (and at some level, the API), it is not possible to have a half-configured model, and our models have no parsing logic internally - but instead is isolated in the extension.