Swift: Simplifying Dictionary Processing

Update: A few weeks after this post, I came across information about Swift 4 updates that I hadn't seen before. A new feature for Dictionaries in Swift 4 is similar to what I was aiming to provide with the code shown in this post. It works well for definitively typed dictionaries (e.g. [String: String]), but it doesn't help with mixed type dictionaries (like [String: AnyObject]) because it still requires two type casts to get from the AnyObject in the dictionary to the object type that needs to be pulled out. For example, pulling a String out of a dictionary of type [String: AnyObject] would look like this:

event.location = dictionary["location", default: "TBD" as AnyObject] as! String

So, I still prefer the technique in this post to the Swift 4 provided solution.

tl;dr

// I wanted to make statements like this:
event.location = dictionary["city"] as? String ?? "TBD"
event.recurring = dictionary["recurring"] as? String ?? false

// More readable with something like this:
event.location = dictionary["city", defaultTo: "TBD"]
event.recurring = dictionary["recurring", defaultTo: false]

// And ended up with this:
event.location = dictionary[("city", defaultTo: "TBD")]
event.recurring = dictionary[("recurring", defaultTo: false)]

// And this for setting values for which nil is OK:
event.revenue = dictionary[("revenue", isType: Float.self)]

-----

Pulling apart a dictionary of data to populate a model object often involves some coding gymnastics. There are several outcomes to consider. For a given key, maybe the dictionary:

  • Contains a value of the expected type.
  • Contains a value but not of the expected type.
  • Doesn't contain a value.

When there is an error, I still want to set a valid value in the model object. So, I often ended up with code like this to pull a value out of a dictionary:

// Just making up an imaginary model object called 'event' 
// that will get some property values from a dictionary.
event.location = dictionary["city"] as? String ?? "TBD"

This is perfectly fine. But it's kind of ugly. Imagine a list of statements like this to fill out an "event" model object that has two dozen properties to pull out of the dictionary. Ugh. I also enjoy taking advantage of Swift's type inference machinery anywhere I can to reduce type casting in code.

Initially I extended Dictionary to include a function that was more readable if not slightly longer code. It looked like this:

public func value<T: Any>(for key: Key, defaultsTo: T) -> T {
    if let value = self[key] as? T {
        return value
    }        

    return defaultsTo
}

// At the call site, it looked like this:
event.location = dictionary.value(for: "city", defaultsTo: "TBD")

Again, this is perfectly fine. Any programmer can guess what this is doing without knowing Swift operator syntax for casting and nil-coalescing. But I've traded in harder to read code for boiler plate in the function name and arguments (imagine a dozen lines repeating the ".value(for:" and "defaultsTo:" parts of that function). I really wanted to continue accessing dictionary values using a subscript notation to avoid too much extra boiler plate. Alas, this was not possible... until Swift 4.

Swift 4 adds support for generic subscript operators. This is exactly what I needed to achieve the type inference I desired with subscript notation on a Dictionary.

But, a subscript can only take a single argument. This limitation can be skirted by using a tuple as the single argument passed to the subscript function. Here is an implementation:

extension Dictionary {
    /**
    Allows subscripting of a dictionary with a tuple that includes the
    dictionary key and a default value. If there is not a value for the
    provided key or if the value found is not of the same type as the
    provided default value, then the default value is returned.
    */
    subscript<T: Any>(tuple: (key: Dictionary.Key, defaultTo: T)) -> T {
        if let value = self[tuple.key] as? T {
            return value
        }

        return tuple.defaultTo
    }
}

// At the call site, this yields:
event.location = dictionary[("city", defaultTo: "TBD")]

// If you prefer, since the argument is a tuple, 
// you can even omit all of the interior argument labels.
even.location = dictionary[("city", "TBD")]

This gets really close to the fanciful outcome I was aiming for.

While the above works well for values that are never nil, it isn't useful for setting variable values for which nil is acceptable. A dictionary lookup and conditional type cast can be used for those cases. For the sake of completeness and code consistency, a second subscript function that doesn't use a default value but still provides type checking can be defined and used like this:

extension Dictionary {
    /**
    Allows subscripting of a dictionary with a tuple that includes the
    dictionary key and the expected type of the value.
    If there is not a value for the provided key or if the value found is
    not of the expected type, then nil is returned.
    */
    subscript<T: Any>(tuple: (key: Dictionary.Key, ofType: T.Type)) -> T? {
        return self[tuple.key] as? T
    }
}

// At the call site, this yields:
event.location = dictionary[("city", ofType: String.self)]

// Or, without the argument label:
event.location = dictionary[("city", String.self)]