Codable enum with default case in Swift 4 Codable enum with default case in Swift 4 ios ios

Codable enum with default case in Swift 4


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 & RawRepresentablewhere 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]


You can drop the raw type for your Type and make unknown case that handles associated value. But this comes at a cost. You somehow need the raw values for your cases. Inspired from this and this SO answers I came up with this elegant solution to your problem.

To be able to store the raw values, we will maintain another enum, but as private:

enum Type {    case text    case image    case document    case profile    case sign    case inputDate    case inputText    case inputNumber    case inputOption    case unknown(String)    // Make this private    private enum RawValues: 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"        // No such case here for the unknowns    }}

Move the encoding & decoding part to extensions:

Decodable part:

extension Type: Decodable {    init(from decoder: Decoder) throws {        let container = try decoder.singleValueContainer()        // As you already know your RawValues is String actually, you decode String here        let stringForRawValues = try container.decode(String.self)         // This is the trick here...        switch stringForRawValues {         // Now You can switch over this String with cases from RawValues since it is String        case RawValues.text.rawValue:            self = .text        case RawValues.image.rawValue:            self = .image        case RawValues.document.rawValue:            self = .document        case RawValues.profile.rawValue:            self = .profile        case RawValues.sign.rawValue:            self = .sign        case RawValues.inputDate.rawValue:            self = .inputDate        case RawValues.inputText.rawValue:            self = .inputText        case RawValues.inputNumber.rawValue:            self = .inputNumber        case RawValues.inputOption.rawValue:            self = .inputOption        // Now handle all unknown types. You just pass the String to Type's unknown case.         // And this is true for every other unknowns that aren't defined in your RawValues        default:             self = .unknown(stringForRawValues)        }    }}

Encodable part:

extension Type: Encodable {    func encode(to encoder: Encoder) throws {        var container = encoder.singleValueContainer()        switch self {        case .text:            try container.encode(RawValues.text)        case .image:            try container.encode(RawValues.image)        case .document:            try container.encode(RawValues.document)        case .profile:            try container.encode(RawValues.profile)        case .sign:            try container.encode(RawValues.sign)        case .inputDate:            try container.encode(RawValues.inputDate)        case .inputText:            try container.encode(RawValues.inputText)        case .inputNumber:            try container.encode(RawValues.inputNumber)        case .inputOption:            try container.encode(RawValues.inputOption)        case .unknown(let string):             // You get the actual String here from the associated value and just encode it            try container.encode(string)        }    }}

Examples:

I just wrapped it in a container structure(because we'll be using JSONEncoder/JSONDecoder) as:

struct Root: Codable {    let type: Type}

For values other than unknown case:

let rootObject = Root(type: Type.document)do {    let encodedRoot = try JSONEncoder().encode(rootObject)    do {        let decodedRoot = try JSONDecoder().decode(Root.self, from: encodedRoot)        print(decodedRoot.type) // document    } catch {        print(error)    }} catch {    print(error)}

For values with unknown case:

let rootObject = Root(type: Type.unknown("new type"))do {    let encodedRoot = try JSONEncoder().encode(rootObject)    do {        let decodedRoot = try JSONDecoder().decode(Root.self, from: encodedRoot)        print(decodedRoot.type) // unknown("new type")    } catch {        print(error)    }} catch {    print(error)}

I put the example with local objects. You can try with your REST API response.


enum Type: String, Codable, Equatable {    case image    case document    case unknown    public init(from decoder: Decoder) throws {        guard let rawValue = try? decoder.singleValueContainer().decode(String.self) else {            self = .unknown            return        }        self = Type(rawValue: rawValue) ?? .unknown    }}