Domain Centric

View Original

Getting default values from Dictionaries in Swift

Swift is around for some time now and introduced interesting constructions that are not widely used in other languages. One of these construction is if let statement that unwraps optionals. Let's consider following example:

let apiDictionary : [String: AnyObject] = ...

var name = "Unknown"

if let apiName = apiDictionary["name"] as? String {
    name = userName
}

Basically our goal is to get a name of the user from some dictionary (probably coming from some api) and in case it doesn't exist we just want to keep the default name.

There are two of problems with that approach. First is fact that probably we've got more fields than just name and we end up with repetitive if let statements that are basically just making our code less readable. The second problem is that just for the sake of unwrapping a value we need to come up with some artificial name for the temporary assignment (and hey, we are not good at naming stuff anyway).

So the question now is - can we do better?

Employing extensions and generics

There are things that I really like about Swift and these include Type safety and Generics. Although Extensions are not on my list (in my opinion should be used carefully as they encourage bad design) in this particular case they help to solve problem in a neat way.

extension Dictionary {
    func get<T>(key: Key, defaultValue: T) -> T {
        if let value = self[key] as? T {
            return value
        }

        return defaultValue
    }
}

This small bit of code actually addresses problems I've highlighted earlier and gives a convenient way of getting object from dictionary with a default value.

So now our bit of code can be refactored to:

let name = apiDictionary.get("name", defaultValue: "Unknown")

What I like about it is fact we don't need to declare name as var and naming problem mentioned earlier is gone. For the cost of not using subscript notation (and calling method instead) we have a convenient way of getting the value. Moreover (thanks to generic) type of the return value is enforced to be the same as the one of the default parameter, yay!

Unfortunately we can't use subscript in exactly same way - I didn't find neat way to make sure return type is correctly enforced, but maybe you did?

Update

On the twitter and in the comments people correctly noticed that in some cases we can just use the nil coalescing operator ?? instead. This works in a case then the type of objects in dictionary is defined. Above post covers scenario when the object are of AnyObject type and in this case coalescing operator will return AnyObject? which needs to be casted to the correct type.