一、前言
Codable
是随 Swift 4.0 推出的,旨在取代现有的 NSCoding 协议,支持结构体、枚举和类,能将 JSON
这种弱数据类型转换成代码中使用的强数据类型。
HandyJSON
在 Codable
推出后已经不再进行维护了,而我们的项目就是依赖于 HandyJSON
来处理 JSON 的序列化
和反序列化
,所以需要逐步迁移到Codable
以避免一些错误。
本文主要介绍一些json转模型的基本概念和我使用过程中遇到的一些解析问题,在此分享一下。
二、基本概念
1. 序列化和反序列化
let responseData = try? JSONSerialization.data(withJSONObject: response)
let obj = try? JSONSerialization.jsonObject(with: data, options: .allowFragments
2. encode和decode
let data = try? JSONEncoder().encode(obj)
let obj = try? JSONDecoder().decode(T.self, from: data)
3. Codable
public typealias Codable = Decodable & Encodabl
4. json转模型
(1) 在用原生Codable协议的时候,需要遵守协议Codable,结构体,枚举,类都可以遵守这个协议,一般使用struct
```swift
struct UserModel:Codable {
var name: String?
var age: Int?
var sex: Bool?
var name_op: String?
}
```
(2) json数据里面的字段和model字段不一致
解决办法:
实现 enum CodingKeys: String, CodingKey {}这个映射关系
struct UserModel:Codable {
var name: String?
var age: Int?
var sex: Bool?
var name_op: String?
var nick: String?
enum CodingKeys: String, CodingKey {
case name
case age
case sex
case name_op
case nick = "nick_name"
}
}
上述代码中,如果后台返回的字段名称叫做nick_name,但是你想用自己命名的nick,就可以用到上述枚举中的映射去替换你想要的字段名字。
(3) 如果你的模型里面带有嵌套关系,比如你的模型里面有个其他模型或者模型数组,那么只要保证嵌套的模型里面依然实现了对应的协议
struct UserModel:Codable {
var name: String?
var age: Int?
var sex: Bool?
var name_op: String?
var nick: String?
var books_op: [BookModel]?
enum CodingKeys: String, CodingKey {
case name
case age
case sex
case name_op
case nick = "nick_name"
}
}
// BookModel
struct BookModel:Codable {
var name:String ?
}
(4) 使用JSONDecoder进行json转model
private func jsonToModel<T: Codable>(_ modelType: T.Type, _ response: Any) -> T? {
guard let data = try? JSONSerialization.data(withJSONObject: response), let info = try? JSONDecoder().decode(T.self, from: data) else {
return nil
}
return info
}
看一下decode源码:
// MARK: - Decoding Values
/// Decodes a top-level value of the given type from the given JSON representation.
///
/// - parameter type: The type of the value to decode.
/// - parameter data: The data to decode from.
/// - returns: A value of the requested type.
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
/// - throws: An error if any value throws an error during decoding.
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {//泛型并且约束遵守协议
let topLevel: Any
do {
//反序列化操作
topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
//调用unbox解码并返回解码后的数据
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
可以看到在转model的时候,先进行一次序列化操作,decode内部又进行一次反序列化操作,苹果这样设计估计是在参数传递的时候想让我们传递字节流
至此就可以使用swift原生协议Codable进行json转model了
5. JSON字符串转模型
let jsonString:String = """
{
"name":"Tomas",
"age":"10",
"gender":"man"
}
"""
如果将它解析成User对象
let jsonData: Data = jsonString.data(using:String.Encoding.utf8)!
let decoder = JSONDecoder()
do {
let user:UserModel = try decoder.decode(UserModel.self,from:jsonData)
} catch {
}
6. JSONSerialization
Serialization 是序列化的意思,JSONSerialization
顾名思义是对JSON进行序列化。
JSONSerialization
是对 JSON 字符串进行序列化和反序列化的工具类。用这个类可以将JSON转成对象,也可以将对象转成JSON。
let dict:[String:Any] = ["name":"jack",
"age":10,
"gender":"man"]
if JSONSerialization.isValidJSONObject(dict) == false {
return
}
let data:Data = try! JSONSerialization.data(withJSONObject: dict, options: .fragmentsAllowed);
// 将data转换成制定model
guard let Model:UserModel = try? JSONDecoder().decode(UserModel.self, from: data) else {
return
}
// 将data转成字符串输出
let string = String(data: data, encoding: String.Encoding.utf8)
print(string)
print(Model.name)
Optional("{"name":"jack","gender":"man","age":10}")
Optional("jack")
- JSON转字典
let jsonString = "{"name":"jack","age":10}";
let data = jsonString.data(using: .utf8)
let dict = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments)
print(dict)
三、Codable 解码设置默认值
References:
Swift – Codable 解码设置默认值
四、常见问题
1. 类型的某个属性有默认值,后台返回的JSON
没有这个属性对应的数据
(1) 正常的Demo
假设我们有一个User
类型,有一个id
属性,和一个是否被当前用户关注的属性isFollowedByCurrentUser
,并实现了Codable
协议,代码如下:
struct User: Codable {
var id: String
var isFollowedByCurrentUser: Bool?
enum CodingKeys: String, CodingKey {
case id
case isFollowedByCurrentUser = "followed"
}
}
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)")
}
毫无疑问,上面的代码是可以解码成功的。
(2) 失败的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
对应的值。
(3) 解决办法:
我们无法保证服务器总是返回完整正确的数据,所以只能从我们客户端去解决问题。
这是最简单方便的方法。这样解码的时候,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
}
五、References
- 【iOS开发】使用Codable时要注意的问题
- Swift 使用Codable协议进行json转模型
- Swift – Codable 解码设置默认值
- Swift之Codable实战技巧
- Codable保姆级攻略
原文地址:https://blog.csdn.net/weixin_43504072/article/details/129088894
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_15005.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!