How do I make an enum Decodable in swift 4?

前端 未结 8 651
死守一世寂寞
死守一世寂寞 2020-11-28 20:16
enum PostType: Decodable {

    init(from decoder: Decoder) throws {

        // What do i put here?
    }

    case Image
    enum CodingKeys: String, CodingKey {
          


        
相关标签:
8条回答
  • 2020-11-28 21:17

    Swift would throw a .dataCorrupted error if it encounters unknown enum value. If your data is coming from a server, it can send you an unknown enum value at any time (bug server side, new type added in an API version and you want the previous versions of your app to handle the case gracefully, etc), you'd better be prepared, and code "defensive style" to safely decode your enums.

    Here is an example on how to do it, with or without associated value

        enum MediaType: Decodable {
           case audio
           case multipleChoice
           case other
           // case other(String) -> we could also parametrise the enum like that
    
           init(from decoder: Decoder) throws {
              let label = try decoder.singleValueContainer().decode(String.self)
              switch label {
                 case "AUDIO": self = .audio
                 case "MULTIPLE_CHOICES": self = .multipleChoice
                 default: self = .other
                 // default: self = .other(label)
              }
           }
        }
    

    And how to use it in a enclosing struct:

        struct Question {
           [...]
           let type: MediaType
    
           enum CodingKeys: String, CodingKey {
              [...]
              case type = "type"
           }
    
    
       extension Question: Decodable {
          init(from decoder: Decoder) throws {
             let container = try decoder.container(keyedBy: CodingKeys.self)
             [...]
             type = try container.decode(MediaType.self, forKey: .type)
          }
       }
    
    0 讨论(0)
  • 2020-11-28 21:18

    Details

    • Xcode 12.0.1 (12A7300)
    • Swift 5.3

    Solution

    import Foundation
    
    enum DecodableEnum<Enum: RawRepresentable> where Enum.RawValue == String {
        case value(Enum)
        case error(DecodingError)
    
        var value: Enum? {
            switch self {
            case .value(let value): return value
            case .error: return nil
            }
        }
    
        var error: DecodingError? {
            switch self {
            case .value: return nil
            case .error(let error): return error
            }
        }
    
        enum DecodingError: Error {
            case notDefined(rawValue: String)
            case decoding(error: Error)
        }
    }
    
    extension DecodableEnum: Decodable {
        init(from decoder: Decoder) throws {
            do {
                let rawValue = try decoder.singleValueContainer().decode(String.self)
                guard let layout = Enum(rawValue: rawValue) else {
                    self = .error(.notDefined(rawValue: rawValue))
                    return
                }
                self = .value(layout)
            } catch let err {
                self = .error(.decoding(error: err))
            }
        }
    }
    

    Usage sample

    enum SimpleEnum: String, Codable {
        case a, b, c, d
    }
    
    struct Model: Decodable {
        let num: Int
        let str: String
        let enum1: DecodableEnum<SimpleEnum>
        let enum2: DecodableEnum<SimpleEnum>
        let enum3: DecodableEnum<SimpleEnum>
        let enum4: DecodableEnum<SimpleEnum>?
    }
    
    let dictionary: [String : Any] = ["num": 1, "str": "blablabla", "enum1": "b", "enum2": "_", "enum3": 1]
    
    let data = try! JSONSerialization.data(withJSONObject: dictionary)
    let object = try JSONDecoder().decode(Model.self, from: data)
    print("1. \(object.enum1.value)")
    print("2. \(object.enum2.error)")
    print("3. \(object.enum3.error)")
    print("4. \(object.enum4)")
    

    0 讨论(0)
提交回复
热议问题