可解码,不解码具有无效值的可选枚举

Decodable, doesn't decode optional enum with invalid value

我定义了这样一个枚举:

enum ClubLevel: Int, Codable {
    case golden = 1, silver, bronze
}

在我的结构中,我有一个类型为 ClubLevel 的可选 属性,当我在 init(from decoder: Decoder) 中解码此 属性 时:

self.clubLevel = try container.decode(ClubLevel?.self, forKey: .clubLevel)

我遇到这个错误:

调试说明:"Cannot initialize ClubLevel from invalid Int value 0", underlyingError: nil"

我想知道即使这个 属性 是可选的,解码器也不会继续

有什么想法吗?

self.clubLevel = try container.decode(ClubLevel?.self, forKey: .clubLevel)

不会尝试解码 ClubLevel,如果不成功则分配 nil。它的作用是:

  1. 尝试为 clubLevel 密钥解码 nil(在 JSON 中表示为 null)。如果不成功,
  2. 尝试为 clubLevel 密钥解码 ClubLevel。如果不成功,
  3. 抛出错误

因此,如果 clubLevel 键的值既不是 nil 也不是有效的 ClubLevel 表示,您将抛出错误。您会注意到,这也意味着如果 clubLevel 键完全丢失(而不是以 nil 的值出现),您将抛出一个错误。

通过 decodeIfPresent:

完成忽略丢失的键
self.clubLevel = try container.decodeIfPresent(ClubLevel.self, forKey: .clubLevel)

现在将:

  1. Return nil 如果容器中缺少 clubLevel 密钥。如果他们的密钥存在,
  2. 尝试为 clubLevel 密钥解码 nil(在 JSON 中表示为 null)。如果不成功,
  3. 尝试为 clubLevel 密钥解码 ClubLevel。如果不成功,
  4. 抛出错误

这是编译器生成的 init(from:) 实现中解码可选值的默认行为。它仍然会在您的情况下引发错误,因为 clubLevel 键的值不是有效的 ClubLevel.

如果您只想尝试解码 ClubLevel,请在因 任何 原因(密钥丢失、无效值、等),那么你想使用 try?:

self.clubLevel = try? container.decode(ClubLevel.self, forKey: .clubLevel)

我遇到了同样的问题,我想我会为任何感兴趣的人添加我的解决方案。

想法是将枚举包装在以下 struct:

struct OptionalDecodableEnum<T>: Decodable where T: RawRepresentable, T.RawValue: Decodable {
    let value: T?

    init(from decoder: Decoder) throws {
        value = T(rawValue: try decoder.singleValueContainer().decode(T.RawValue.self))
    }
}

主要好处是您不必在每次需要可选枚举时都实施 Decodable。您也不需要带有额外括号的可选链接。

但是,您必须 return 内部 value 属性 才能使用它。例如

struct Foo: Decodable {
    enum ClubLevel: Int, Codable {
        case golden = 1, silver, bronze
    }

    let clubLevel: OptionalDecodableEnum<ClubLevel>
}

let foo = try jsonDecoder.decode(Foo.self, from: data)
print(String(describing: foo.clubLevel.value))

我一直在寻找一种方法来解决此 post 中描述的类似问题 - 仅针对枚举值数组而不是单个枚举值。由于这是第一个 post 在寻找这个问题的答案时出现,所以我想在这里分享我的解决方案,以防它可以帮助遇到类似问题的人:)

有问题的例子:

{
  "exampleArray": [
     "FirstExample",
     "SecondExample",
     "abcde123",
     "FourthExample"
  ]
}
// ...
// Usage
init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    enumArray = try? container.decodeEnumArray([EnumType].self, forKey: .enumArray)
}

// ...

extension KeyedDecodingContainer {

    func decodeEnumArray<T: RawRepresentable>(_: [T].Type, forKey key: Self.Key) throws -> [T] where T.RawValue: Decodable {
        return try decode([T.RawValue].self, forKey: key).map { T(rawValue: [=11=]) }.compactMap { [=11=] }
    }
}

enumArray 将是 [FirstExample, SecondExample, FourthExample]