enum PostType: Decodable {

    init(from decoder: Decoder) throws {

        // What do i put here?
    }

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

我要怎么做才能完成这个? 此外,让我们说我把情况改为这样:

case image(value: Int)

我如何使这符合解码?

这是我的完整代码(不工作)

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

当前回答

如果遇到未知的enum值,Swift会抛出一个. datacorurt错误。如果你的数据来自服务器,它可以在任何时候给你一个未知的枚举值(错误服务器端,在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)
      }
   }

其他回答

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

我所做的很简单:

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,并使你的内部列表数组私有:

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-customization - colls-in swift-a344e25d0bb0

如何使枚举与相关类型符合可编码

这个答案类似于@Howard Lovatt的答案,但避免了创建PostTypeCodableForm结构体,而是使用苹果提供的KeyedEncodingContainer类型作为编码器和解码器的属性,这减少了样板文件。

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)

为了扩展@Toka的回答,你也可以在enum中添加一个原始的可表示值,并使用默认的可选构造函数来构建没有开关的enum:

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
}

如果指定了无效的enum值,也可以轻松地扩展它以抛出错误,而不是默认值。这一变化的要点可以在这里找到:https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128。 使用Swift 4.1/Xcode 9.3编译和测试代码。

如果遇到未知的enum值,Swift会抛出一个. datacorurt错误。如果你的数据来自服务器,它可以在任何时候给你一个未知的枚举值(错误服务器端,在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)
      }
   }

特性

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

细节

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