Encoding and Decoding Enums with Associated Values Using Codable (Swift 4)

Alex Türk
3 min readAug 25, 2017

--

During a Swift 4 migration of a download manager framework, I rewrote my model objects (NSObject subclasses conforming to NSCoding). My DownloadItems have a state that’s represented by an enum with associated values:

With Swift 3 I had to write a wrapper object conforming to NSCoding to persist this enum. With Codable this workaround is no longer needed since I can add Codable conformance to the enum directly. Now there are different ways to implement this. I’m still not quite sure what the best solution is, but maybe someone can use my current approach.

Before taking a look at the DownloadState, let’s see a simpler example of a Value enum:

There is a one-to-one mapping between the type and the case so we can set up a container that looks like this:

// Pseudo dictionary
["string": String, "int": Int, "data": Data, "double": Double]

After all, KeyedDecodingContainer and KeyedEncodingContainer are just very strict dictionaries, where subscript keys are strongly typed String enums (conforming to CodingKey) and getters and setters leverage Swift’s error handling system to provide meaningful errors about why getting or setting a value for a certain key might fail. So at least for me it makes sense to think about the containers as dictionaries with fixed keys :)

Implementing Codable for Value might look something like this:

We use the decodeIfPresent function of KeyedDecodingContainer, because only one key will be set. The disadvantage is that we don’t use a switch statement for decoding so the compiler can’t scream at us when we add a new case and forget decoding it.

Moreover, it doesn’t work for the DownloadState example, because we have cases with and without associated values. My approach was to use the “base” case and the associated values as CodingKeys and then have a private enum hosting all the base cases as simple Strings. Let’s see how that looks:

What I really like is that the compiler can completely autogenerate the Codable implementation of Base since it’s just a String enum. Unfortunately, we still have to provide the Codable implementation for DownloadState 🙂.

The following pseudo dictionaries are all valid representations of a DownloadState.

// Pseudo dictionaries
["base": "canceled"]
-> DownloadState.canceled
["base": "paused", "pausedReason": "waitingForWifi"]
-> DownloadState.paused(reason: .waitingForWifi)
["base": "downloading", "downloadProgress": 0.5]
-> DownloadState.downloading(progress: 0.5]

Does this scale? No… If there are multiple associated values involved, this is actually quite a complex type and should be handled just like a nested struct or class. Consider the following example where Route represents the different screens of a music application with a home and a detail page (to play the music):

Super simple right? But adding every associated value to the CodingKeys

has some drawbacks:

  • It feels semantically wrong to have the associated values on the same hierarchy as the basic cases (just like in the DownloadState example, but even more prominent)
  • If the enum gets even more cases (and more associated values) this gets even harder to read and maintain
  • If this was a class or a struct you would never think about encoding the information of a nested type within the parent.

So how can we solve this? I think adding a struct for every case with associated values is a clean approach and helps split up responsibility:

Since we delegate the Codable conformance down to every associated value struct the CodingKeys only contain one key per case (with associated values). The code seems pretty repetitive but it’s simple and follows a certain pattern. You could also use Sourcery to automate this process.

It also works with an enum that uses Codable types as associated values:

Again, we have one key for the base case and one coding key per case with associated values. The structs handle encoding and decoding themselves and the types within the struct are themselves responsible for implementing Codable.

Happy 🐟ing!

--

--

Alex Türk

iOS Developer ● Swift enthusiast ● Coffee lover ☕️ ● @fruitcoder