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

时间:2017-12-20 12:31:09

标签: swift swift4 codable

我已经定义了这样的枚举:

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

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

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

我遇到这个错误:

debugDescription:"Cannot initialize ClubLevel from invalid Int value 0", underlyingError: nil"

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

任何想法?

3 个答案:

答案 0 :(得分:6)

该行

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

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

  1. 尝试为nil密钥解码null(在JSON中表示为clubLevel)。如果不成功,
  2. 尝试为ClubLevel密钥解析clubLevel。如果不成功,
  3. 发生错误
  4. 因此,如果clubLevel键的值既不是nil也不是有效ClubLevel表示,那么您将收到错误。您会注意到,这也意味着如果clubLevel密钥完全丢失(而不是以nil的值存在),您将收到错误。

    使用decodeIfPresent

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

    现在:

    1. 如果容器中缺少nil密钥,请返回clubLevel。如果他们的钥匙存在,
    2. 尝试为nil密钥解码null(在JSON中表示为clubLevel)。如果不成功,
    3. 尝试为ClubLevel密钥解析clubLevel。如果不成功,
    4. 发生错误
    5. 这是在编译器生成的init(from:)实现中解码选项的默认行为。由于clubLevel密钥的值不是有效ClubLevel,因此在您的情况下仍然会出错。

      如果您只想尝试解码ClubLevel,请为任何原因(密钥丢失,无效值等)的解码失败分配nil,然后您想使用try?

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

答案 1 :(得分:1)

我遇到了同样的问题,并认为我会为有兴趣的人添加解决方案。

想法是将枚举包装在以下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。您也不需要带额外括号的可选链接。

但是,当您想使用内部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))

答案 2 :(得分:0)

我一直在寻找一种解决本文所述类似问题的方法-仅用于枚举值数组,而不是单个枚举值。因为这是寻找该问题的答案时出现的第一篇文章,所以我想在这里分享我的解决方案,以防它可能帮助有类似问题的人:)

问题示例:

{
  "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: $0) }.compactMap { $0 }
    }
}

enumArray将为[FirstExample, SecondExample, FourthExample]