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

问题描述 投票:0回答:4
我定义了一个这样的枚举:

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"



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

有什么想法吗?

swift swift4 codable
4个回答
15
投票
线路

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

不会尝试解码

ClubLevel

,如果不成功则分配 
nil
。它的作用是:

    尝试解码
  1. nil
    (在 JSON 中表示为 
    null
    )以获得 
    clubLevel
     键。如果不成功,
  2. 尝试将
  3. ClubLevel
     解码为 
    clubLevel
     键。如果不成功,
  4. 抛出错误
因此,如果

clubLevel

 键的值既不是 
nil
 也不是有效的 
ClubLevel
 表示形式,则会抛出错误。您会注意到,这也意味着如果 
clubLevel
 键完全丢失(而不是存在 
nil
 值),您将会抛出错误。

通过

decodeIfPresent

 忽略丢失的键:

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

现在将:

如果容器中缺少
    nil
  1. 键,请返回
    clubLevel
    。如果钥匙存在,
    尝试解码 
  2. nil
  3. (在 JSON 中表示为
    null
    )以获得
    clubLevel
    键。如果不成功,
    尝试将 
  4. ClubLevel
  5. 解码为
    clubLevel
    键。如果不成功,
    抛出错误
  6. 这是编译器生成的
init(from:)

实现中解码选项的默认行为。在您的情况下,它仍然会抛出错误,因为

clubLevel
键的值不是有效的
ClubLevel

如果您只想尝试解码

ClubLevel

,在因任何

原因(密钥缺失、值无效等)而失败的解码上分配 
nil
,那么您需要使用 
try?:
self.clubLevel = try? container.decode(ClubLevel.self, forKey: .clubLevel)

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

4
投票
这个想法是将枚举包装在以下

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

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

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]


我能够将 Guy Kogus 的答案转换为属性包装器,这消除了使用此代码时的一些摩擦。

0
投票
@propertyWrapper struct OptionalCodableEnum<T>: Codable where T: RawRepresentable, T.RawValue: Codable { var wrappedValue: T? init(wrappedValue: T?) { self.wrappedValue = wrappedValue } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let rawValue = try? container.decode(T.RawValue.self), let result = T(rawValue: rawValue) { wrappedValue = result } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(wrappedValue?.rawValue) } }

用法如下:
    struct MyCodable: Codable {
        enum MyCodableEnum: Int, Codable {
            case first = 0, second
        }
        
        @OptionalCodableEnum
        var anEnumValue: MyCodableEnum?
    }


© www.soinside.com 2019 - 2024. All rights reserved.