当 swift iOS 中出现意外值时,如何将 nil 设置为可编码枚举

Mru*_*ank 6 enums swift codable

我正在使用Codable我的 WebRequest 响应,它返回一些预定义的字符串或数字。所以,我使用枚举来实现这些。但是,当一些意外值到达响应时,我的 Codable 无法解码。

这里有一些代码可以更好地理解。

class WebUser: Codable, Equatable {
    static func == (lhs: WebUser, rhs: WebUser) -> Bool {
        return lhs.id == rhs.id
    }
    ...
    var mobileNumberPrivacy: CommonPrivacyOption?
    var emailPrivacy: CommonPrivacyOption?
    var dobPrivacy: CommonPrivacyOption?
    ...
}

enum CommonPrivacyOption: Int, CaseIterable, Codable {
    case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends

    //Does not help this optional init function
    /*init?(rawValue: Int) {
        switch rawValue {
            case 1: self = .privacyOnlyMe
            case 2: self = .privacyPublic
            case 3: self = .privacyFriends
            case 4: self = .privacyFriendsOfFriends
            default: return nil
        }
    }*/

}
Run Code Online (Sandbox Code Playgroud)

但有时我从 WebServer 得到 0,因为dobPrivacy当时我遇到了DecodingError.dataCorrupted上下文异常Cannot initialize CommonPrivacyOption from invalid Int value 0

正如我期望的那样,当我获得其他值 1/2/3/4 时, dobPrivacy nil 。

编辑:

let dict1 = [
    "id": 2,
    "mobileNumberPrivacy": 3,
    "emailPrivacy": 4,
    "dobPrivacy": 0 // Works perfectly with 1
]
do {
    let data1 = try JSONSerialization.data(withJSONObject: dict1, options: .prettyPrinted)
    let user1 = try JSONDecoder().decode(WebUser.self, from: data1)
    print("User1 created")
}
catch DecodingError.dataCorrupted(let context) {
    print(context.codingPath)
    print(context.debugDescription)
}
catch {
    print(error.localizedDescription)
}
Run Code Online (Sandbox Code Playgroud)

我使用相同的 Codable WebUser 对象来获取个人资料详细信息、搜索用户等。因此,有时 WebRequest 的响应中可能不会出现另外一个密钥。

rob*_*off 11

我建议编写一个属性包装器来为您处理这个问题。

具体来说,让我们编写一个名为 的属性包装器NilOnDecodingError,将 any 转换DecodingErrornil.

以下是 的声明NilOnDecodingError

@propertyWrapper
public struct NilOnDecodingError<Wrapped> {
    public init(wrappedValue: Wrapped?) {
        self.wrappedValue = wrappedValue
    }

    public var wrappedValue: Wrapped?
}
Run Code Online (Sandbox Code Playgroud)

我们将它定义为包装任何类型,存储Optional.

现在我们可以将其符合Decodable类型WrappedDecodable

extension NilOnDecodingError: Decodable where Wrapped: Decodable {
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            wrappedValue = .some(try container.decode(Wrapped.self))
        } catch is DecodingError {
            wrappedValue = nil
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

EncodableWrapped类型为:时我们可能还希望它是Encodable

extension NilOnDecodingError: Encodable where Wrapped: Encodable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        if let value = wrappedValue {
            try container.encode(value)
        } else {
            try container.encodeNil()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以包装适当的字段WebUser

class WebUser: Codable {
    let id: String

    @NilOnDecodingError
    var mobileNumberPrivacy: CommonPrivacyOption?

    @NilOnDecodingError
    var emailPrivacy: CommonPrivacyOption?

    @NilOnDecodingError
    var dobPrivacy: CommonPrivacyOption?
}
Run Code Online (Sandbox Code Playgroud)

为了进行测试,我们需要打印解码后的用户的字段:

extension WebUser: CustomStringConvertible {
    var description: String {
        return """
        WebUser(
            id: \(id),
            mobileNumberPrivacy: \(mobileNumberPrivacy.map { "\($0)" } ?? "nil"),
            emailPrivacy: \(emailPrivacy.map { "\($0)" } ?? "nil")),
            dobPrivacy: \(dobPrivacy.map { "\($0)" } ?? "nil")))
        """
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以尝试一下:

let json = """
{
    "id": "mrugesh",
    "mobileNumberPrivacy": 1,
    "emailPrivacy": 2,
    "dobPrivacy": 1000
}
"""

let user = try! JSONDecoder().decode(WebUser.self, from: json.data(using: .utf8)!)
print(user)
Run Code Online (Sandbox Code Playgroud)

输出:

WebUser(
    id: mrugesh,
    mobileNumberPrivacy: privacyOnlyMe,
    emailPrivacy: privacyPublic),
    dobPrivacy: nil))
Run Code Online (Sandbox Code Playgroud)


Joa*_*son 1

在我看来,编译器为枚举类型选择了错误的 init,而不是init(rawValue)使用它init(from:)进行解码(这在某种程度上是有道理的)

这是一个解决方案,我们通过使用解码原始值然后创建枚举项的自定义来覆盖init(from)WebUser行为

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .mobileNumberPrivacy), let mobileNumberPrivacy = CommonPrivacyOption(rawValue: value) {
        self.mobileNumberPrivacy = mobileNumberPrivacy
    }
    if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .emailPrivacy), let emailPrivacy = CommonPrivacyOption(rawValue: value) {
        self.emailPrivacy = emailPrivacy
    }
    if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .dobPrivacy), let dobPrivacy = CommonPrivacyOption(rawValue: value) {
        self.dobPrivacy = dobPrivacy
    }
}
Run Code Online (Sandbox Code Playgroud)

下面是一个小例子

extension WebUser: CustomStringConvertible {
    var description: String {
        "Mobile: \(mobileNumberPrivacy?.rawValue), email: \(emailPrivacy?.rawValue), dob: \(dobPrivacy?.rawValue)"
    }
}

let data = """
{
    "mobileNumberPrivacy": 1,
    "dobPrivacy": 0
}
""".data(using: .utf8)!

do {
    let result = try JSONDecoder().decode(WebUser.self, from: data)
    print(result)
} catch {
    print(error)
}
Run Code Online (Sandbox Code Playgroud)

手机:可选(1),电子邮件:无,出生日期:无


当然,如果您可以改变主意,将 0 转换为 nil 那么我建议您扩展枚举以支持 0 值

enum CommonPrivacyOption: Int, CaseIterable, Codable {
    case none = 0
    case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
}
Run Code Online (Sandbox Code Playgroud)

然后它应该可以开箱即用,您不需要编写任何自定义代码。