在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
。
网友评论