Codable编解码数据时的一些边界问题

5,256 阅读2分钟

我正在参加「掘金·启航计划」

键名与属性名不匹配

Apple提倡开发者在编码时使用驼峰命名,如果后端返回的数据中字段采用的是蛇形命名,比如:

{
  "nick_name": "张三",
  "formal_name": "张老三"
}

那么我们要如何统一编码风格呢?

Apple贴心的为我们提供了不同的解码策略,其中就包含了处理这种字段使用蛇形命名方案的策略,只要在解码前置处理即可 JSONDecoder().keyDecodingStrategy = .convertFromSnakeCase 然而要使用这个方法,需要保证所有的 JSON 响应都遵守蛇形命名规则,然而问题在于,在不断地迭代当中,我们无法保证亲爱的后端同事会不会在某一个版本心血来潮地对命名规则做出调整。所以尽管这种方法很方便,但是并不可取。

更具有可持续性的策略是使用 CodingKeys 指定明确的映射。

extension Person {
    private enum CodingKeys: String, CodingKey {
        case nickName = "nick_name"
        case formalName = "formal_name"
    }
}

拓展: JSONDecoder为日期解析提供了一个方便的方法dateDecodingStrategy,我们可以根据后端返回的时间类型来指定合适的策略,直接在数据解析的过程当中,悄悄地将时间转为我们需要的。有兴趣的读者可以自行查阅。

空值处理

在JSON解析的过程中,遇到null是常事,Swift中可以将模型属性设为可选类型,比如var name: String? ,或者给定默认值var name: String = ""

随机类型处理

相比JSONSerialization,Codable十分不灵活,几乎所有和 JSONSerialization 有关的字典都是 [String:Any] 类型的,把它变成 Decodable 类型有点困难。

假设我们需要一个这样的数据模型

struct Person: Decodable {
  var name: String
  var age: Int
  var others: [String: Any]
}

当你建好了一个这样的数据模型,编译器不出意外的报错了,原因是[String: Any] 类型不遵循 Codable 协议,因为它的值类型 Any 不遵从 Codable。不幸的是, [String: Codable] 也不行,因为需要指定确切的类型。

如果我们明确所有数据的值都是一种类型的,比如说都是 String,那么把类型直接定义成 [String: String]就可以了。然而如果想要处理混合类型的值,甚至包括嵌套的数组和字典,那就只能寻找另外的解决方案了。

使用类型无关的AnyCodable 是一种解决方案,它的接口和 AnyHashable 差不多。

示例:

struct Person: Decodable {
  var name: String
  var age: Int
  var others: [String: AnyDecodable]
}

参考

使用Swift Codable进行高效的数据编解码