iosswiftenumsswift4codable

Codable enum with default case in Swift 4


I have defined an enum as follows:

enum Type: String, Codable {
    case text = "text"
    case image = "image"
    case document = "document"
    case profile = "profile"
    case sign = "sign"
    case inputDate = "input_date"
    case inputText = "input_text"
    case inputNumber = "input_number"
    case inputOption = "input_option"

    case unknown
}

that maps a JSON string property. The automatic serialization and deserialization works fine, but I found that if a different string is encountered, the deserialization fails.

Is it possible to define an unknown case that maps any other available case?

This can be very useful, since this data comes from a RESTFul API that, maybe, can change in the future.


Solution

  • You can extend your Codable Type and assign a default value in case of failure:

    enum Type: String {
        case text,
             image,
             document,
             profile,
             sign,
             inputDate = "input_date",
             inputText = "input_text" ,
             inputNumber = "input_number",
             inputOption = "input_option",
             unknown
    }
    extension Type: Codable {
        public init(from decoder: Decoder) throws {
            self = try Type(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown
        }
    }
    

    edit/update:

    Xcode 11.2 • Swift 5.1 or later

    Create a protocol that defaults to last case of a CaseIterable & Decodable enumeration:

    protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
    where RawValue: Decodable, AllCases: BidirectionalCollection { }
    
    extension CaseIterableDefaultsLast {
        init(from decoder: Decoder) throws {
            self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? Self.allCases.last!
        }
    }
    

    Playground testing:

    enum Type: String, CaseIterableDefaultsLast {
        case text, image, document, profile, sign, inputDate = "input_date", inputText = "input_text" , inputNumber = "input_number", inputOption = "input_option", unknown
    }
    

    let types = try! JSONDecoder().decode([Type].self , from: Data(#"["text","image","sound"]"#.utf8))  // [text, image, unknown]