Handling unknown enum cases

Even though they are sometimes overused, enums are still a great tool. Thanks to Codable they can easily be decoded from JSON sent by your backend.

This is great until you need to add a new case to the enum. Then you have to either deal with versioning your API or you need to tell your users to update the app. In some cases you have no choice, but in other cases it would be fine to just ignore the new value and fall back to some default behavior.

Here are a few ways I used to realize such a scenario:

RawRepresentable struct

The simplest way would be to replace enums with a RawRepresentable struct:

struct ItemType: RawRepresentable, Codable {
    var rawValue: String
    
    static let house = ItemType(rawValue: "house")
    static let tree = ItemType(rawValue: "tree")
}
Code language: JavaScript (javascript)

In code this can be used just like an enum would, you’d only have to add a default case to every switch statement.

This can lead to situations where a different subset of those cases is handled in different places. Sometimes that’s fine, but in other cases you want to ensure to handle all known cases everywhere. So you need an actual enum in order for the compiler to be able to do exhaustiveness checks.

Enum with “Unknown” case

To do this we simply can manually implement the decoding and map unknown values to a default case:

enum ItemType: String, Codable {
    case house
    case tree
    case unknown

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let string = try container.decode(String.self)
        switch string {
        case "house": self = .house
        case "tree": self = .tree
        default: self = .unknown
        }
    }
}
Code language: JavaScript (javascript)

Implementing this for many enums becomes pretty boring. Luckily we can share the implementation between all enums by writing a protocol with a default implementation:

protocol UnknownDecodable: Decodable, RawRepresentable {
    static var unknown: Self { get }
}

extension UnknownDecodable where RawValue: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let raw = try container.decode(RawValue.self)
        self = Self(rawValue: raw) ?? .unknown
    }
}Code language: JavaScript (javascript)

Using this we can define our enum like we would normally and get that behavior for free by just conforming to the UnknownDecodable protocol. Note that our enum case unknown can fulfill the protocol requirement of static var unknown: Self:

enum ItemType: String, Codable, UnknownDecodable {
    case house
    case tree
    case unknown
}Code language: JavaScript (javascript)

Decoding unknown cases as nil

A final solution I sometimes find useful is decoding the enum as optional and mapping unknown values to nil

This is easy to do if we manually implement decoding for the struct that contains this enum. But this would lead to a lot of (possibly repeated) boilerplate code we don’t like to write. Instead we can use a property wrapper:

@propertyWrapper
struct DecodeUnknownAsNil<Enum: RawRepresentable> where Enum.RawValue: Codable {
    var wrappedValue: Enum?
}

extension DecodeUnknownAsNil : Codable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let raw = try container.decode(Enum.RawValue.self)
        wrappedValue = Enum(rawValue: raw)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue?.rawValue)
    }
}

extension KeyedDecodingContainer {
    func decode<Enum>(_ type: DecodeUnknownAsNil<Enum>.Type, forKey key: Key) throws -> DecodeUnknownAsNil<Enum> {
        return try decodeIfPresent(type, forKey: key) ?? .init(wrappedValue: nil)
    }
}

extension DecodeUnknownAsNil: Equatable where Enum: Equatable {}
extension DecodeUnknownAsNil: Hashable where Enum: Hashable {}
Code language: JavaScript (javascript)

The extension on KeyedDecodingContainer is necessary to handle missing or null values in the JSON.

When to use which

I choose the RawRepresentable struct when I don’t need to make a decision based on that value. This could be for looking up some resource or other values in a dictionary.

The DecodeUnknownAsNil property wrapper is great if the enum value is optional anyways and you can handle the unknown case just as if there was no value at all.

For most cases I would chose the UnknownDecodable protocol though. If I make my property optional I can distinguish between no value and an unknown value. And if the property is not optional I know the decoding will fail so I can catch bugs on the server side where required properties are missing.