如何使枚举可通过其案例名称而不是其原始值进行解码?

How do you make an enum decodable by its case name and not its raw value?

如果我有这样的枚举:

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

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

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
}

但我仍然遇到错误

The data couldn't be read because it isn't in the correct format.


编辑 2

我尝试像@Lutz 所说的那样在 CodingKeys 中明确定义原始值,但我遇到了同样的错误。以防万一 JSONDecoder 不允许碎片化 JSON,我尝试使用 SomeEnum 的数组(#"["case1", "case2"]"#,这也没有用。

我调查了一下,这里的问题是您在 JSON 结果中看到的是编码的 value,而不是 key。因此,添加 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: { [=11=].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