Future-Proof enum decoding

When working with String enums that have a finite number of cases, it’s crucial to include an unknown case as a catch-all for potential future values. This practice helps future-proof our apps against updates or changes on the backend that may introduce new enum values.

To achieve this, we provide a custom initializer for the enum. This initializer tries to decode the received value and maps it to the corresponding enum case. If it encounters a value not defined in the enum, it defaults to the unknown case.

Here’s an example illustrating this approach:

enum UserStatus: String {
    case pendingAuthorization, verified, unknown

    init(from decoder: Decoder) throws {
        guard let rawValue = try? decoder.singleValueContainer().decode(String.self) else {
            Logger.log(.err, "Can't decode a String value from \(Self.self).")
            self = .unknown
            return
        }
        if let knownValue = UserStatus(rawValue: rawValue) {
            self = knownValue
        } else {
            Logger.log(.err, "Unknown value (\(rawValue)) for \(Self.self).")
            self = .unknown
        }
    }
}

extension UserStatus: Decodable {}

In this example, if the backend sends an unfamiliar value like userStatus = "blocked", the app won’t crash due to a failed decoding. Instead, the UserStatus for this particular instance will be set to unknown, allowing the app to continue operating normally.

Testing

You could add a simple unit test to make sure that the decoding will work correctly:

func test_userStatus_decoding() {
    // Given
    let decoder = JSONDecoder()
    let dict: [String: UserStatus] = [
        #" "pendingAuthorization" "#: .pendingAuthorization,
        #" "verified" "#: .verified,
        #" "blocked" "#: .unknown,
        #" "manu" "#: .unknown
    ]

    dict.forEach { json, expectedValue in
        // When
        let actualValue = try? decoder.decode(UserStatus.self, from: json.data(using: .utf8)!)
        // Then
        XCTAssertEqual(actualValue, expectedValue)
    }
}
Future-Proof enum decoding | manu.show
Tags: iOS testing
Share: Twitter LinkedIn