【iOS开发】使用Codable时要注意的问题

2019-05-08  本文已影响0人  Lebron_James

在Swift 4推出Codable之后,我们基本上可以抛弃字典转模型的第三方库了。在我自己的使用过程中,发现了一些会导致无法解码JSON的细节问题。在此跟大家分享下。

一、类型的某个属性有默认值,后台返回的JSON没有这个属性对应的数据

正常的Demo

User

假设我们有一个User类型,有一个id属性,和一个是否被当前用户关注的属性isFollowedByCurrentUser,并实现了Codable协议,代码如下:

struct User: Codable {
    var id: String
    var isFollowedByCurrentUser: Bool?

    enum CodingKeys: String, CodingKey {
        case id
        case isFollowedByCurrentUser = "followed"
    }
}
解码

我们的JSON数据如下:

let jsonString = """
  {
    "id":"efa41bae-25fa-428b-99c1-6d3c1b178875",
    "followed": true
  }
"""

JSONDecoder进行解码:

let decoder = JSONDecoder()

let data = jsonString.data(using: .utf8)!

do {
    let user = try decoder.decode(User.self, from: data)
    print(user)
} catch {
    print("error: \(error)")
}

毫无疑问,上面的代码是可以解码成功的。

失败的Demo

有些时候,后台返回的JSON数据可能缺少某些字段,假设缺少了followed,那么现在的JSON数据为:

let jsonString = """
  {
    "id":"efa41bae-25fa-428b-99c1-6d3c1b178875"
  }
"""

这时我们用上面的JSONDecoder进行解码,也是可以解码成功的,只不过isFollowedByCurrentUser的值为nil而已。

现在问题来了,我们看回User类型。通常我们在某个类型添加一个Bool属性时,一般会给他一个默认值false,所以我们会习惯的把User写成:

struct User: Codable {

    var id: String
    var isFollowedByCurrentUser = false

    enum CodingKeys: String, CodingKey {
        case id
        case isFollowedByCurrentUser = "followed"
    }
}

这时如果我们再用JSONDecoder把缺少followed字段的JSON数据转成User的话,是无法转成功的,错误如下:

error: keyNotFound(CodingKeys(stringValue: "followed", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"followed\", intValue: nil) (\"followed\").", underlyingError: nil))

JSONDecoder在JSON数据中无法找到followed对应的值。

解决办法

我们无法保证服务器总是返回完整的数据,所以只能从我们客户端去解决问题。

1. 把类型的所有属性都定义为Optional类型

这是最简单方便的方法。这样解码的时候,JSONDecoder发现JSON没有对应的数据,就自动把这个属性设置为nil

2. 实现Decodable的初始化函数,并使用decodeIfPresent来解码

正常情况下,我们定义了CodingKeys之后,不需要手动实现init(from decoder: Decoder) throws这个初始化函数的,JSONDecoder就可以正常解码。但是我们把isFollowedByCurrentUser定义成一个非可选类型,我们必须实现这个初始化函数,才能正常解码:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    id = try container.decode(String.self, forKey: .id)
    isFollowedByCurrentUser = try container.decodeIfPresent(Bool.self, forKey: .isFollowedByCurrentUser) ?? false
}

欢迎加入我管理的Swift开发群:536353151

上一篇下一篇

猜你喜欢

热点阅读