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 }}