One way to allow an enumeration with associated values to conform to the
Codable protocol is to represent it in a struct which is
Codable. Another, better way is to use the
Codable container provided by Apple. I’m going to show how to do both step-by-step, using the
Barcode example from the Enumeration section of the Swift Programming book. (I won’t make the joke that you should stop reading this to go read that while I wait for you, I’m just assuming you’ve already done it. Surely you’ve also read about the
Codable protocol as well.) My hope is that, by including an explanation of an inferior method, some insight will be gained as to what framework is doing (more or less) under the hood.
enums come prepopulated with raw values, like for example this one in which each planet case is assigned an
In this case, archiving the enum instance is as easy as archiving an integer. But what about those enum types with associated values?
Barcode implementation is simple:
This enum however contains more information than can be represented by a simple raw value type like a
String or an
Int and so it cannot conform to
Codable without additional instructions as to how each case is to be coded. One way to provide this information is to create an intermediate type that knows how to do just that.
(By the way, these instructions, however they are provided, are not something we ought to expose to the wider world. Ideally, all that users need to know is that
Codable, so let’s keep those implementation details private, ok?)
The Intermediate Type
I’m going to start by declaring a struct called
Coding, nested inside an extension of
Barcode and conforming to
Codable. It’s not
Codable yet, but we’ll get it there. This type needs to be able to represent all the information that is possible to represent in the original
Barcode enum, and so presumably it will need a different property for each case, where the type of the property matches the type of its case’s associated value.
But this doesn’t quite work. The point of the intermediate type is to create a representation of
Barcode which is
Codable, but the type of
(Int, Int, Int, Int) which is a tuple and tuples are not
One workaround for this is to represent the tuple as an
Array<Int> since collections of
Codable types are themselves
Codable (tuples are not collections, nor should they be), but I’m going to create another, separate intermediate type to represent
(Int, Int, Int, Int): a struct with four
Int properties to represent each
Int of the
upc value, and then we’ll be back in business.
At this point, our intermediate
Barcode.Coding type is officially
Codable, since its properties are all
Codable. But we’re not quite done. We have a
Codable representation of our original type, now we need methods to convert one into the other: a function to encode a
Barcode instance into a
Barcode.Coding instance and a function to decode a
Barcode.Coding instance into a
To do this requires one more tweak to our intermediate type. If there’s one thing an enumeration is good at, it’s this: An instance is one case or another, but never both, guaranteed. We ought to include this feature, insofar as we can, in our
Barcode.Coding type, since we’re trying to represent an enum. The natural way to do this is to have its properties be optional so that when a
Barcode instance is a
.upc, then the
qrCode property of its
Barcode.Coding struct can be
nil, and vice versa.
We’ll write an explicit
init method in the body of the struct (not in an extension because we want to replace the memberwise initializer in order to narrow the ways this struct can be created) and this
init method will guarantee that exactly one of the properties can ever receive a value and all other properties will remain
nil. This guarantee will be provided by switching on the original enum.
We now have the ability to convert a
Barcode to a
Barcode.Coding type which is
Codable, we now need implement the conversion in the other direction. We just need a method on
Barcode.Coding that returns a
Barcode. We’ll make it
throw for reasons that will become clear in a moment.
How do we create a
Barcode from a
Barcode.Coding? We will switch on a tuple of all our properties, and use pattern matching to find the cases where one property value is
.some and all the others
.none, then return the
Barcode instance for that case initialized with the corresponding value. We’ll reach the
default case in situations where either more than one property is non-
nil or else all are
nil, so then we’ll need to throw an error.
After all that work, we’re finally ready to write the code that will conform our
Barcode type to
Codable. First, we only need to implement two methods, one each for
Decodable, and second, we need to explicitly declare conformance.
Both of these methods are easy now that we’ve finished the hard work of creating a
Codable type to represent a
init method merely needs to try to create a
Barcode.Coding instance from the
decoder parameter, and then convert that instance into a
On the other hand, the
encode method needs to create a
Barcode.Coding instance from
self, and call the
encode method of that instance with the
Now we have a fully
Codable type. Let’s test it.
Protip: We try to be aesthetic and all here on proxpero.com. But seriously, don’t use pretty-printing for testing JSON equality. Also, until
Equatableis actually implemented on
==operator is not going to work. I’m going to leave that as an exercise for you 😉.
Even with only two cases, this was not a trivial amount of code. It’s not difficult; in fact, it’s very like boilerplate. With larger numbers of cases, writing all the tedious code required to get an enumeration with associated values to be
Codable will make you ask yourself: What am I really getting for all this typing? Turns out, there’s a better solution. (Special thanks to @stephencelis for showing this to me.)
One of my favorite Stack Overflow answers (I can’t find the link) was a response to a question about how many lines of codes to write in one day. The answer was something like
On good days, it’s measured in negative numbers. On better days, in higher negative numbers.
Smaller units are more easily understandable, less code means fewer bugs. But often we can’t start with the smaller codebase. The first priority is to get something working, and iterate afterwards, reducing our material until we’ve reached the bare necessities. One consequence of this approach is that the bloated, middle period nevertheless provides intuition about the workings of the final result.
Our first solution used an intermediate,
Codable type that we implemented ourselves. As it happens, the
Decoder protocols already provide a struct for the same purpose. Some configuration is still required but at least we don’t need to create our own
Coding struct. Let’s start over.
Encoder protocol specifies only a handful of requirements. One is that a conforming type implement this function:
It is a function with a generic type parameter and it returns a container which is generic over that same type. This container, an instance of
KeyedEncodingContainer, serves the same purpose as our
Coding struct from earlier. Looking at the return type of this function, we see that
KeyedEncodingContainer is generic over some
Key, looking at the definition of this type, we see that this generic parameter must be constrained to the
CodingKey protocol provides a mapping from the names of the properties of the encoded type to the keys of the underlying storage.
We’ve specified a private enum called
CodingKeys. (The name can be anything, but “CodingKeys” follows a convention established by Apple. Note that it is plural whereas usually enum names are singular.) The enum provide raw values for its cases, either
Ints and must declare conformance to the
CodingKey protocol. For convenience I’ve chosen cases that match the property names of
Barcode, but again these are arbitrary, additionally you could even specify a raw value different from the case. Now we can implement the two methods needed to conform to
Codable. We’ll start with encoding.
To begin with, we get the
encoder. Then, we switch on
self is a
Barcode.upc, we get the four
Int values out, and then we’ll try to encode an array of the four upc values in the container and the key for that value in the container will be the rawValue of
Barcode.CodingKeys.upc. (I made an intermediate type to store the four upc values in the earlier implementation. This time I’m just using an array.) Likewise, if
self is a
Barcode.qrCode, we get the
String value, and then try to encode it into the
container using the key
Warning: I’ve used
.upcin two different places in the code above. The compiler knows they are different, but I wanted to explicitly point it out. One is a
Barcode.upcand the other is a
Barcode.CodingKey.upc. The same is true of the
Decoder protocol similarly provides a
KeyedDecodingContainer, which we can use in the init method.
Again, we get the container first and then try to extract the appropriate values from it. If we can assign to
codes an array of integers using the key
Barcode.CodingKeys.upc (actually, the
rawValue of it), then we will initialize a
Barcode.upc whose associated values map to the four elements of the
Array<Int> that we extracted. If that doesn’t work, we try to assign to
String using as the key
Barcode.CodingKeys.qrCode (again it is really
rawValue of the
.qrCode coding key) and use that
String to create the corresponding
Barcode.qrCode and assign it to
self. If neither of those works then we’ll throw a custom error.
Here’s the full solution:
I’m pretty happy with Apple’s solution to the problem of archiving Swift value types. The fact that it doesn’t just work in every case is not a reason not to use it. Some assembly is required. Using Apple’s
KeyedEncoderContainer types helps minimize the burden on developers who need to serialize enums with associated values.
- This code is available as a gist. You can paste it into a playground.
- Apple has a good article documenting
- The proposal on Swift Evolution.
- The actual implementation of
Codablein the Swift repo.
- The actual implementation of
JSONEncoderin the Swift repo.
- Greg Heo wrote a detailed investigation of the JSONEncoder.