Blog

Swift Tip: Codable Enums

If we have an enum wih associated types, the compiler can't automatically generate Codable conformance yet. For example, let's create an enum that can be one of two values:

								enum Either<A,B> {
    case left(A)
    case right(B)
}

							

If we use the latest Swift toolchain, we can conditionally make it conform to Codable (see this gist), but even in the current Swift release we can make it work by adding Codable constraints to our generic parameters:

								enum Either<A: Codable, B: Codable> {
    case left(A)
    case right(B)
} 

							

To encode, we create two CodingKeys, and use those keys to tag the value before encoding it:

								extension Either: Encodable {
    enum CodingKeys: CodingKey {
        case left
        case right
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .left(let value):
            try container.encode(value, forKey: .left)
        case .right(let value):
            try container.encode(value, forKey: .right)
        }
    }
}

							

To decode, we mirror the process. First, we try to decode using the .left key. If that doesn't work, we catch the error, and try to decode using the .right key. If that doesn't work either, the error will be propagated (just like a regular decoding error):

								extension Either: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            let leftValue =  try container.decode(A.self, forKey: .left)
            self = .left(leftValue)
        } catch {
            let rightValue =  try container.decode(B.self, forKey: .right)
            self = .right(rightValue)
        }
    }
}

							

Now we can make use of our new Codable conformance:

								import Foundation

let values: [Either<String,Int>] = [
    .left("Hi"),
    .right(42)
]


let encoder = JSONEncoder()
let data = try! encoder.encode(values)
let str = String(data: data, encoding: .utf8)!
// [{"left":"Hi"},{"right":42}]

							

And we can see that we also can read it back:

								let decoder = JSONDecoder()
let result = try! decoder.decode([Either<String,Int>].self, from: data)
// [.left("Hi"), .right(42)]

							

The technique above doesn't just work for the Either type, but can be made to work for any enum with associated values, as long as all associated values are Codable themselves. Just add a case to CodingKeys for each case in your enum.

For more information on Codable, see our book Advanced Swift, which has a full chapter on encoding and decoding.


Stay up-to-date with our newsletter or follow us on Twitter .

Back to the Blog

Recent Posts