Articles, podcasts and news about Swift development, by John Sundell.

Codable synthesis for Swift enums

Published on 09 Sep 2021
Basics article available: Enums

One of the major advantages of Swift’s built-in Codable API is how the compiler is able to automatically synthesize many different encoding and decoding implementations when using it. In many cases, all that we have to do to enable a Swift type to be serialized into formats like JSON is to mark it as Codable, and the compiler takes care of the rest.

Let’s take a look at how that automatic synthesis works for enums specifically, and how that part of the system has been upgraded in Swift 5.5.

Raw representable enums

Enums generally come in two variants — those that are backed by raw values (such as Int or String), and those that contain associated values. Ever since the introduction of Codable in Swift 4.0, enums that belong to the former category have always supported compiler synthesis.

So, for example, let’s say that we’re working on an app that includes the following String-backed enum, which conforms to Codable:

enum MediaType: String, Codable {
    case article
    case podcast
    case video
}

Since the compiler is able to automatically synthesize all of the code needed to encode and decode enums with raw values, we typically don’t have to write any more code than that ourselves — meaning that we’re now free to use the above enum within other Codable types, like this:

struct Item: Codable {
    var title: String
    var url: URL
    var mediaType: MediaType
}

If we now were to encode an instance of the above Item type into JSON, then we’d get the following kind of output (since MediaType values will automatically be encoded and decoded using the raw String values that back them):

{
    "title": "Swift by Sundell",
    "url": "https://swiftbysundell.com/podcast",
    "mediaType": "podcast"
}

So far so good. But what if we instead wanted to encode or decode an enum that supports associated values? Let’s take a look at that next.

Associated values

Before Swift 5.5, if we wanted to make an enum that contains associated values conform to Codable, then we’d have to write all of that code manually. However, that’s no longer the case, as the compiler has received an upgrade that now makes it capable of auto-synthesizing serialization code for such enums as well.

For example, the following Video enum can now be made Codable without requiring any custom code on our part:

enum Video: Codable {
    case youTube(id: String)
    case vimeo(id: String)
    case hosted(url: URL)
}

To see what the above type looks like when encoded, let’s create an instance of a VideoCollection that stores an array of Video values:

struct VideoCollection: Codable {
    var name: String
    var videos: [Video]
}

let collection = VideoCollection(
    name: "Conference talks",
    videos: [
    .youTube(id: "ujOc3a7Hav0"),
    .vimeo(id: "234961067")
]
)

If we then encode the above collection value into JSON, then we’ll get the following result:

{
    "name": "Conference talks",
    "videos": [
        {
            "youTube": {
    "id": "ujOc3a7Hav0"
}
        },
        {
            "vimeo": {
    "id": "234961067"
}
        }
    ]
}

So, by default, when we let the compiler automatically synthesize the Codable conformance for an enum with associated values, then the names of our cases and the labels for the associated values within them will be used when calculating that type’s serialization format.

For unlabelled associated values, underscored numeric keys (like _0, _1, and so on) will be used by default.

Key customization

Just like when working with structs and classes, we’re able to customize what keys that will be used when encoding or decoding an enum’s cases and associated values, and we can even use that capability to omit certain cases entirely.

For example, let’s say that we wanted to extend our Video enum to add support for local videos, but that there’s no reasonable way for us to serialize the data for those videos.

While we could always create a completely separate type for representing such videos, we could also use a nested CodingKeys enum to tell the compiler to ignore the local case when generating our Video type’s Codable implementation.

Here we’ve done just that, and we’ve also customized the hosted case so that it’s referred to as custom when serialized:

enum Video: Codable {
    case youTube(id: String)
    case vimeo(id: String)
    case hosted(url: URL)
    case local(LocalVideo)
}

extension Video {
    enum CodingKeys: String, CodingKey {
        case youTube
        case vimeo
        case hosted = "custom"
    }
}

If needed, we could even customize what keys that are used for the associated values within a specific case. For example, here’s how we could declare that we’d like the youTube case’s id value to be serialized as youTubeID:

extension Video {
    enum YouTubeCodingKeys: String, CodingKey {
        case id = "youTubeID"
    }
}

The above YouTubeCodingKeys enum is matched to our youTube case by name, so if we also wanted to customize the vimeo case, then we could add a VimeoCodingKeys enum for that.

Conclusion

Although Codable’s automatic synthesis does have its limits (particularly when working with serialized formats that are vastly different from how we’d like to organize the data within our Swift types), it’s incredibly convenient — and is often particularly useful when working with locally stored values, such as when caching values on disk, or when reading bundled configuration files.

Regardless, the fact that enums with associated values can now join the auto-synthesis party is definitely a good thing in terms of consistency, and should prove to be quite useful within many different code bases — including my own.

Got questions, comments, or feedback? You’re more than welcome to reach out via email.

Thanks for reading!