【问题标题】:How do I make an enum Decodable in swift 4?如何在 swift 4 中使枚举可解码?
【发布时间】:2017-11-18 17:27:55
【问题描述】:
enum PostType: Decodable {

    init(from decoder: Decoder) throws {

        // What do i put here?
    }

    case Image
    enum CodingKeys: String, CodingKey {
        case image
    }
}

我要做什么来完成这个? 另外,假设我将case 更改为:

case image(value: Int)

我如何使它符合 Decodable ?

编辑这是我的完整代码(不起作用)

let jsonData = """
{
    "count": 4
}
""".data(using: .utf8)!

        do {
            let decoder = JSONDecoder()
            let response = try decoder.decode(PostType.self, from: jsonData)

            print(response)
        } catch {
            print(error)
        }
    }
}

enum PostType: Int, Codable {
    case count = 4
}

最终编辑 另外,它将如何处理这样的枚举?

enum PostType: Decodable {
    case count(number: Int)
}

【问题讨论】:

    标签: swift enums


    【解决方案1】:

    这里有很多好的方法,但我还没有看到一个讨论具有多个值的枚举,尽管它可以从示例中推断出来——也许有人可以找到这个方法的用途:

    import Foundation
    
    enum Tup {
      case frist(String, next: Int)
      case second(Int, former: String)
      
      enum TupType: String, Codable {
        case first
        case second
      }
      enum CodingKeys: String, CodingKey {
        case type
        
        case first
        case firstNext
        
        case second
        case secondFormer
      }
      
    }
    
    extension Tup: Codable {
      init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let type = try values.decode(TupType.self, forKey: .type)
        switch type {
        case .first:
          let str = try values.decode(String.self, forKey: .first)
          let next = try values.decode(Int.self, forKey: .firstNext)
          self = .frist(str, next: next)
        case .second:
          let int = try values.decode(Int.self, forKey: .second)
          let former = try values.decode(String.self, forKey: .secondFormer)
          self = .second(int, former: former)
        }
      }
      
      func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        switch self {
        case .frist(let str, next: let next):
          try container.encode(TupType.first, forKey: .type)
          try container.encode(str, forKey: .first)
          try container.encode(next, forKey: .firstNext)
        case .second(let int, former: let former):
          try container.encode(TupType.second, forKey: .type)
          try container.encode(int, forKey: .second)
          try container.encode(former, forKey: .secondFormer)
        }
        
      }
    }
    
    let example1 = Tup.frist("123", next: 90)
    do {
      let encoded = try JSONEncoder().encode(example1)
      print(encoded)
      
      let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
      print("decoded 1 = \(decoded)")
    }
    catch {
      print("errpr = \(error.localizedDescription)")
    }
    
    
    let example2 = Tup.second(10, former: "dantheman")
    
    do {
      let encoded = try JSONEncoder().encode(example2)
      print(encoded)
      
      let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
      print("decoded 2 = \(decoded)")
    }
    catch {
      print("errpr = \(error.localizedDescription)")
    }
    
    

    【讨论】:

      【解决方案2】:

      特点

      • 使用简单。 Decodable 实例中的一行:行 eg let enum: DecodableEnum<AnyEnum>
      • 使用标准映射机制解码:JSONDecoder().decode(Model.self, from: data)
      • 涵盖接收未知数据的情况(例如,如果您收到意外数据,映射Decodable 对象不会失败)
      • 处理/传递mappingdecoding 错误

      详情

      • Xcode 12.0.1 (12A7300)
      • 斯威夫特 5.3

      解决方案

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

      使用示例

      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)")
      

      【讨论】:

      【解决方案3】:

      这很简单,只需使用隐式分配的 StringInt 原始值。

      enum PostType: Int, Codable {
          case image, blob
      }
      

      image 编码为0blob 编码为1

      或者

      enum PostType: String, Codable {
          case image, blob
      }
      

      image 编码为"image"blob 编码为"blob"


      这是一个如何使用它的简单示例:

      enum PostType : Int, Codable {
          case count = 4
      }
      
      struct Post : Codable {
          var type : PostType
      }
      
      let jsonString = "{\"type\": 4}"
      
      let jsonData = Data(jsonString.utf8)
      
      do {
          let decoded = try JSONDecoder().decode(Post.self, from: jsonData)
          print("decoded:", decoded.type)
      } catch {
          print(error)
      }
      

      【讨论】:

      • 我尝试了您建议的代码,但它不起作用。我已经编辑了我的代码以显示我正在尝试解码的 JSON
      • 不能单独对枚举进行编码/解码。它必须嵌入到结构中。我添加了一个示例。
      • 我会将其标记为正确。但是上面问题的最后一部分没有得到回答。如果我的枚举看起来像这样呢? (以上编辑)
      • 如果您使用具有关联类型的枚举,您必须编写自定义编码和解码方法。请阅读Encoding and Decoding Custom Types
      • 关于“一个枚举不能单独编码/解码。”,它似乎在iOS 13.3得到解决。我在iOS 13.3iOS 12.4.3 中进行测试,它们的行为不同。在iOS 13.3 下,可以单独对枚举进行en-/decode。
      【解决方案4】:

      如何使关联类型的枚举符合Codable

      此答案类似于@Howard Lovatt 的答案,但避免创建PostTypeCodableForm 结构,而是使用KeyedEncodingContainer 类型provided by Apple 作为EncoderDecoder 的属性,从而减少样板。

      enum PostType: Codable {
          case count(number: Int)
          case title(String)
      }
      
      extension PostType {
      
          private enum CodingKeys: String, CodingKey {
              case count
              case title
          }
      
          enum PostTypeCodingError: Error {
              case decoding(String)
          }
      
          init(from decoder: Decoder) throws {
              let values = try decoder.container(keyedBy: CodingKeys.self)
              if let value = try? values.decode(Int.self, forKey: .count) {
                  self = .count(number: value)
                  return
              }
              if let value = try? values.decode(String.self, forKey: .title) {
                  self = .title(value)
                  return
              }
              throw PostTypeCodingError.decoding("Whoops! \(dump(values))")
          }
      
          func encode(to encoder: Encoder) throws {
              var container = encoder.container(keyedBy: CodingKeys.self)
              switch self {
              case .count(let number):
                  try container.encode(number, forKey: .count)
              case .title(let value):
                  try container.encode(value, forKey: .title)
              }
          }
      }
      

      此代码适用于 Xcode 9b3。

      import Foundation // Needed for JSONEncoder/JSONDecoder
      
      let encoder = JSONEncoder()
      encoder.outputFormatting = .prettyPrinted
      let decoder = JSONDecoder()
      
      let count = PostType.count(number: 42)
      let countData = try encoder.encode(count)
      let countJSON = String.init(data: countData, encoding: .utf8)!
      print(countJSON)
      //    {
      //      "count" : 42
      //    }
      
      let decodedCount = try decoder.decode(PostType.self, from: countData)
      
      let title = PostType.title("Hello, World!")
      let titleData = try encoder.encode(title)
      let titleJSON = String.init(data: titleData, encoding: .utf8)!
      print(titleJSON)
      //    {
      //        "title": "Hello, World!"
      //    }
      let decodedTitle = try decoder.decode(PostType.self, from: titleData)
      

      【讨论】:

      【解决方案5】:

      实际上,上面的答案确实很棒,但是它们缺少许多人在不断开发的客户端/服务器项目中需要的一些细节。我们开发一个应用程序,而我们的后端随着时间的推移不断发展,这意味着一些枚举案例将改变这种演变。因此,我们需要一种能够解码包含未知情况的枚举数组的枚举解码策略。否则解码包含数组的对象就会失败。

      我做的很简单:

      enum Direction: String, Decodable {
          case north, south, east, west
      }
      
      struct DirectionList {
         let directions: [Direction]
      }
      
      extension DirectionList: Decodable {
      
          public init(from decoder: Decoder) throws {
      
              var container = try decoder.unkeyedContainer()
      
              var directions: [Direction] = []
      
              while !container.isAtEnd {
      
                  // Here we just decode the string from the JSON which always works as long as the array element is a string
                  let rawValue = try container.decode(String.self)
      
                  guard let direction = Direction(rawValue: rawValue) else {
                      // Unknown enum value found - ignore, print error to console or log error to analytics service so you'll always know that there are apps out which cannot decode enum cases!
                      continue
                  }
                  // Add all known enum cases to the list of directions
                  directions.append(direction)
              }
              self.directions = directions
          }
      }
      

      奖励:隐藏实施 > 将其设为集合

      隐藏实现细节总是一个好主意。为此,您只需要多一点代码。诀窍是使DirectionsList 符合Collection 并使您的内部list 数组私有:

      struct DirectionList {
      
          typealias ArrayType = [Direction]
      
          private let directions: ArrayType
      }
      
      extension DirectionList: Collection {
      
          typealias Index = ArrayType.Index
          typealias Element = ArrayType.Element
      
          // The upper and lower bounds of the collection, used in iterations
          var startIndex: Index { return directions.startIndex }
          var endIndex: Index { return directions.endIndex }
      
          // Required subscript, based on a dictionary index
          subscript(index: Index) -> Element {
              get { return directions[index] }
          }
      
          // Method that returns the next index when iterating
          func index(after i: Index) -> Index {
              return directions.index(after: i)
          }
      }
      

      您可以在 John Sundell 的这篇博文中了解更多关于遵守自定义集合的信息:https://medium.com/@johnsundell/creating-custom-collections-in-swift-a344e25d0bb0

      【讨论】:

        【解决方案6】:

        要扩展@Toka 的答案,您也可以向枚举添加原始可表示值,并使用默认的可选构造函数来构建没有switch 的枚举:

        enum MediaType: String, Decodable {
          case audio = "AUDIO"
          case multipleChoice = "MULTIPLE_CHOICES"
          case other
        
          init(from decoder: Decoder) throws {
            let label = try decoder.singleValueContainer().decode(String.self)
            self = MediaType(rawValue: label) ?? .other
          }
        }
        

        它可以使用允许重构构造函数的自定义协议进行扩展:

        protocol EnumDecodable: RawRepresentable, Decodable {
          static var defaultDecoderValue: Self { get }
        }
        
        extension EnumDecodable where RawValue: Decodable {
          init(from decoder: Decoder) throws {
            let value = try decoder.singleValueContainer().decode(RawValue.self)
            self = Self(rawValue: value) ?? Self.defaultDecoderValue
          }
        }
        
        enum MediaType: String, EnumDecodable {
          static let defaultDecoderValue: MediaType = .other
        
          case audio = "AUDIO"
          case multipleChoices = "MULTIPLE_CHOICES"
          case other
        }
        

        如果指定了无效的枚举值,它也可以很容易地扩展为抛出错误,而不是默认值。此处提供了此更改的要点:https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128
        代码使用 Swift 4.1/Xcode 9.3 编译和测试。

        【讨论】:

        • 这就是我要寻找的答案。
        • 感谢您保持简单 :)
        【解决方案7】:

        @proxpero 的一个更简洁的响应变体是将解码器表述为:

        public init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            guard let key = values.allKeys.first else { throw err("No valid keys in: \(values)") }
            func dec<T: Decodable>() throws -> T { return try values.decode(T.self, forKey: key) }
        
            switch key {
            case .count: self = try .count(dec())
            case .title: self = try .title(dec())
            }
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            switch self {
            case .count(let x): try container.encode(x, forKey: .count)
            case .title(let x): try container.encode(x, forKey: .title)
            }
        }
        

        这允许编译器详尽地验证这些情况,并且在编码值与键的预期值不匹配的情况下也不会抑制错误消息。

        【讨论】:

        • 我同意这样更好。
        • 简洁的解决方案,我喜欢它
        【解决方案8】:

        如果遇到未知的枚举值,Swift 会抛出 .dataCorrupted 错误。如果您的数据来自服务器,它可以随时向您发送未知的枚举值(错误服务器端、API 版本中添加的新类型以及您希望应用程序的先前版本能够优雅地处理这种情况等),你最好做好准备,编写“防御式”代码来安全地解码你的枚举。

        这里是一个关于如何做的例子,有或没有相关值

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

        以及如何在封闭结构中使用它:

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

        【讨论】:

        • 谢谢,您的回答更容易理解。
        • 这个答案对我也有帮助,谢谢。可以通过让你的枚举继承自 String 来改进它,然后你不需要切换字符串
        • 简单的答案,直截了当。感谢这工作完美!
        【解决方案9】:

        你可以做你想做的事,但它有点复杂:(

        import Foundation
        
        enum PostType: Codable {
            case count(number: Int)
            case comment(text: String)
        
            init(from decoder: Decoder) throws {
                self = try PostTypeCodableForm(from: decoder).enumForm()
            }
        
            func encode(to encoder: Encoder) throws {
                try PostTypeCodableForm(self).encode(to: encoder)
            }
        }
        
        struct PostTypeCodableForm: Codable {
            // All fields must be optional!
            var countNumber: Int?
            var commentText: String?
        
            init(_ enumForm: PostType) {
                switch enumForm {
                case .count(let number):
                    countNumber = number
                case .comment(let text):
                    commentText = text
                }
            }
        
            func enumForm() throws -> PostType {
                if let number = countNumber {
                    guard commentText == nil else {
                        throw DecodeError.moreThanOneEnumCase
                    }
                    return .count(number: number)
                }
                if let text = commentText {
                    guard countNumber == nil else {
                        throw DecodeError.moreThanOneEnumCase
                    }
                    return .comment(text: text)
                }
                throw DecodeError.noRecognizedContent
            }
        
            enum DecodeError: Error {
                case noRecognizedContent
                case moreThanOneEnumCase
            }
        }
        
        let test = PostType.count(number: 3)
        let data = try JSONEncoder().encode(test)
        let string = String(data: data, encoding: .utf8)!
        print(string) // {"countNumber":3}
        let result = try JSONDecoder().decode(PostType.self, from: data)
        print(result) // count(3)
        

        【讨论】:

        • 有趣的破解
        猜你喜欢
        • 1970-01-01
        • 2020-08-10
        • 2019-01-06
        • 1970-01-01
        • 2022-06-16
        • 2018-09-16
        • 1970-01-01
        • 1970-01-01
        • 2018-05-14
        相关资源
        最近更新 更多