我已经为我的项目中的一些类实现了自定义JSON解码.我已经设置了常用的CodingKeys枚举,并实现了自定义的编码和解码函数.一切正常.

但是我还为解码过程实现了一些方便的函数,这些函数可以捕获JSONDecoder的KeyedDecodingContainer抛出的任何错误,并返回一些正常的结果.因为这些函数不需要在类之间变化,所以我想将它们移出类,但大多数示例将编码键定义为类中的硬编码枚举.

下面是要解码的类:

class User : Equatable, Codable
{
    var ID: String = ""
    var pw: String = ""
    var username:  String = ""
    var firstName: String = ""
    var lastName: String = ""
    var EMail: String = ""
    var phoneNbr: String = ""
    var avatarURL: String = ""
    var mediaServiceID: String = ""
    var validated: Bool = false // E-mail is confirmed.

    // Equatable
    static func == (lhs: User, rhs: User) -> Bool
    {
        return lhs.ID == rhs.ID
    }

    // Codable
    enum CodingKeys: String, CodingKey
    {
        case ID = "ID"
        case pw = "pw"
        case username = "username"
        case firstName = "firstName"
        case lastName = "lastName"
        case EMail = "EMail"
        case phoneNbr = "phoneNbr"
        case avatarURL = "avatarURL"
        case mediaServiceID = "mediaServiceID"
        case validated = "validated"
    }

    required init(from decoder: Decoder) throws
    {
        var container: KeyedDecodingContainer<CodingKeys>
        do
        {
            container = try decoder.container(keyedBy: CodingKeys.self)
        }
        
        ID =                safeStringDecode(container: container, forKey: .ID)
        pw =                safeStringDecode(container: container, forKey: .pw)
        username =          safeStringDecode(container: container, forKey: .username)
        firstName =         safeStringDecode(container: container, forKey: .firstName)
        lastName =          safeStringDecode(container: container, forKey: .lastName)
        EMail =             safeStringDecode(container: container, forKey: .EMail)
        phoneNbr =          safeStringDecode(container: container, forKey: .phoneNbr)
        avatarURL =         safeStringDecode(container: container, forKey: .avatarURL)
        mediaServiceID =    safeStringDecode(container: container, forKey: .mediaServiceID)
        validated =         safeBoolDecode(container: container, forKey: .validated)
    }

    func safeStringDecode(container: KeyedDecodingContainer<CodingKeys>, forKey: CodingKeys) -> String
    {
        var result: String
        do
        {
            result = try container.decode(String.self, forKey: forKey)
        }
        catch
        {
            result = ""
        }
        
        return result
    }
    
    func safeBoolDecode(container: KeyedDecodingContainer<CodingKeys>, forKey: CodingKeys) -> Bool
    {
        // First try int, because that's what MySQL/MariaDB return for bools.
        var result: Int
        do
        {
            result = try container.decode(Int.self, forKey: forKey)
            return result == 1
        }
        catch
        {
            // Let's see if it's a 'true'/'false' string.
            var stringResult: String
            do
            {
                stringResult = try container.decode(String.self, forKey: forKey)
                return stringResult == "true"
            }
            catch
            {
                return false
            }
        }
    }

I want to move those safeDecode methods out of this class and into a general utility namespace. But that means I can't hard-code the coding keys as an enum in the class, so I need to pass them in as an array or dictionary. It seems like it should be simple, but thus far I haven't seen a succinct solution. #3 in this post seems close, but I don't really understand some of the logic: How do I use custom keys with Swift 4's Decodable protocol?

"safeDecode"函数的基本原理是,如果任何值丢失或为nil,解码对象将完全失败,这是完全可能的,因为这些列在我的数据库中是可为nullable的.为了防止没有方便的功能,我想我必须在一个单独的do/catch对中 for each 成员进行解码try .非常乏味但我并不知道你在try ?语法来逐行捕获此内容.因此,现在我只需要布尔帮助器来处理不同数据库的布尔类型.我把它放在一个KeyedDecodingContainer扩展中,就像@Alexander善意建议的那样.

推荐答案

前言:我强烈警告不要提供这样的缺省值.如果你最终得到了一个像User(username: "", firstName: "", lastName: "", EMail: "", phoneNbr: "", ...)这样的物体,那有什么安全的呢?这些帮助器函数允许悄悄地从裂缝中溜走,这是无稽之谈.

尽管如此,我将更笼统地回答这个问题,以展示如何在SWIFT中共享这样的代码.

Approach 1: mix it in with a protocol extension

// An empty marker protocol, which requires the conforming type to
// be decodable
protocol DecodingHelpers: Decodable {
  associatedtype CodingKeys: CodingKey
}

// Mark `User` with the protocol
extension User: DecodingHelpers {}

// Extend all `DecodingHelpers`-conforming types with the helper functions
extension DecodingHelpers {
  func safeStringDecode(container: KeyedDecodingContainer<CodingKeys>, forKey: CodingKeys) -> String {
    (try? container.decode(String.self, forKey: forKey)) ?? ""
  }
  
  func safeBoolDecode(container: KeyedDecodingContainer<CodingKeys>, forKey: CodingKeys) -> Bool {
    do {
      // First try int, because that's what MySQL/MariaDB return for bools.
      return try container.decode(Int.self, forKey: forKey) == 1
    }
    catch {
      do {
        // Let's see if it's a 'true'/'false' string.
        return try container.decode(String.self, forKey: forKey) == "true"
      }
      catch {
        return false
      }
    }
  }
}

Approach 2: Move these to an extension

您可以将这些助手作为方法转储到使用它们的所有模型上,而只需将它们更改为KeyedDecodingContainer上的扩展:

extension KeyedDecodingContainer {
  func safeStringDecode(forKey: K) -> String {
    (try? decode(String.self, forKey: forKey)) ?? ""
  }
  
  func safeBoolDecode(forKey: K) -> Bool {
    do {
      // First try int, because that's what MySQL/MariaDB return for bools.
      return try decode(Int.self, forKey: forKey) == 1
    }
    catch {
      do {
        // Let's see if it's a 'true'/'false' string.
        return try decode(String.self, forKey: forKey) == "true"
      }
      catch {
        return false
      }
    }
  }
}

// Usage:
class User: Equatable, Codable
  // ...

  required init(from decoder: Decoder) throws {
    let container =  try decoder.container(keyedBy: CodingKeys.self)
    
    ID             = container.safeStringDecode(forKey: .ID)
    pw             = container.safeStringDecode(forKey: .pw)
    username       = container.safeStringDecode(forKey: .username)
    firstName      = container.safeStringDecode(forKey: .firstName)
    lastName       = container.safeStringDecode(forKey: .lastName)
    EMail          = container.safeStringDecode(forKey: .EMail)
    phoneNbr       = container.safeStringDecode(forKey: .phoneNbr)
    avatarURL      = container.safeStringDecode(forKey: .avatarURL)
    mediaServiceID = container.safeStringDecode(forKey: .mediaServiceID)
    validated      = container.safeBoolDecode(forKey: .validated)
  }
}

Approach 3: Use property wrappers

这是我最喜欢的方法,因为它可以完全消除定义一个定制的init(from:)初始化器的需要!

MetaCodable包中签出Default宏,以供参考:https://github.com/SwiftyLab/MetaCodable/blob/a6011c3337f573b04b29b8591b507de7e6e4ed8d/Sources/MetaCodable/Default.swift

Swift相关问答推荐

启用完成并发判断以及如何解决警告(续)

如何将泛型函数存储到变量中?

SWIFT闭包使用的是陈旧的值,即使它是S@转义

按SWIFT中每个ID的最大值进行筛选/排序

在不传递参数的情况下使用Init方法?

允许视图在视图内更改

在 init(from decoder: Decoder) 方法中访问原始 JSON 数据

为什么我的 tableView 没有推送到 tableView 内的 detailViewController?

在领域 SwiftUI 异常中使用 Apple 登录:无法识别的 Select 器发送到实例

为什么 String.contains 在我导入 Foundation 时表现不同?

如何在 Combine 合并运算符的输出上使用 eraseToAnyPublisher

如何在 SWIFTUI 中旋转修剪?

如何将回调传递给 View init

AVPlayer 在 iOS 15.4 中寻求 completionHandler 返回 false

如何快速判断设备方向是横向还是横向?

在 Swift 中获取双精度的小数部分

Swiftwhere数组扩展

iOS/Swift:如何检测 UITextField 上的touch 动作

在 Swiftui 中是否有一种简单的方法可以通过捏合来放大图像?

如何快速将弹出框调整为表格视图中内容的大小?