首页 > 解决方案 > 你如何让一个枚举可以通过它的案例名称而不是它的原始值来解码?

问题描述

如果我有这样的枚举:

enum SomeEnum: String {
  case case1 = "raw value 1"
  case case2 = "raw value 2"
}

如何Decodable通过使用案例名称(case1case2)而不是原始值来使其符合?例如,我可以像这样使用它:

let data = Data("\"case1\"".utf8)
let decodedEnum = try! JSONDecoder().decode(SomeEnum.self, from: data) // SomeEnum.case1

编辑

我添加了这个来SomeEnum喜欢@Alexander 所说的:

enum CodingKeys: String, CodingKey {
  case case1, case2
}

但我仍然收到错误

无法读取数据,因为它的格式不正确。


编辑 2

CodingKeys我尝试像@Lutz 所说的那样明确定义原始值,但我得到了同样的错误。以防万一JSONDecoder不允许分段 JSON,我尝试使用SomeEnums (数组#"["case1", "case2"]"#,这也不起作用。

标签: swiftenumsdecodable

解决方案


我调查了一下,这里的问题是您在 JSON 结果中看到的是一个编码,而不是一个。因此,添加CodingKeys将无济于事。

一个稍微复杂的解决方案使用自定义协议和相应的扩展来实现目标。

有了它,您可以声明:

    enum Test: String, CaseNameCodable {
        case one = "Number One"
        case two = "Number Two"
    }

它会做你需要的。

下面概述了一个完整的工作示例(在 Xcode 11.2 的 Playground 中为我工作):

    import Foundation

    // A custom error type for decoding...
    struct CaseNameCodableError: Error {
        private let caseName: String

        init(_ value: String) {
            caseName = value
        }

        var localizedDescription: String {
            #"Unable to create an enum case named "\#(caseName)""#
        }
    }

    //
    // This is the interesting part:
    //

    protocol CaseNameCodable: Codable,  RawRepresentable ,  CaseIterable {}

    extension CaseNameCodable {

        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            let value = try container.decode(String.self)
            guard let raw = Self.allCases.first(where: { $0.caseName == value })?.rawValue else { throw CaseNameCodableError(value) }
            self.init(rawValue: raw)!
        }

        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encode(caseName)
        }

        private var caseName: String {
            return "\(self)"
        }
    }

    //
    // Now you can use the protocol CaseNameCodable just like you
    // would use Codable (on RawRepresentable enums only)
    //

    enum Test: String, CaseNameCodable {
        case one = "Number One"
        case two = "Number Two"
    }

    // EXAMPLE:

    // Create a test value
    let testValue = Test.one

    // encode it and convert it to a String
    let jsonData = try! JSONEncoder().encode(testValue)
    let jsonString = String(data: jsonData, encoding: .utf8)!

    print (jsonString) // prints: "one"

    // decode the same data to produce a decoded enum instance
    let decodedTestValue = try JSONDecoder().decode(Test.self, from: jsonData)

    print(decodedTestValue.rawValue) // prints: Number One


推荐阅读