Codable synthesis for Swift enums
Basics article available: EnumsOne 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!