一、前言

Codable 是随 Swift 4.0 推出的,旨在取代现有的 NSCoding 协议支持结构体、枚举和类,能将 JSON这种弱数据类型转换代码使用的强数据类型

HandyJSONCodable 推出后已经不再进行维护了,而我们项目就是依赖于 HandyJSON 处理 JSON 的序列化序列化,所以需要逐步迁移Codable以避免一些错误

本文主要介绍一些json模型基本概念和我使用过程中遇到的一些解析问题,在此分享一下。

二、基本概念

1. 序列化和反序列化

let responseData = try? JSONSerialization.data(withJSONObject: response)

let obj = try? JSONSerialization.jsonObject(with: data, options: .allowFragments

2. encodedecode

let data = try? JSONEncoder().encode(obj)
let obj = try? JSONDecoder().decode(T.self, from: data)

3. Codable

public typealias Codable = Decodable & Encodabl

实际上,Codable就是指的编码解码协议

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进行jsonmodel

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
    }

先使用JSONSerialization进行一次序列化操作

看一下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")
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"
    }
}

我们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)")
}

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

(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))

JSONDecoderJSON数据中无法找到followed对应的值。

(3) 解决办法:

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

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
}

五、References

原文地址:https://blog.csdn.net/weixin_43504072/article/details/129088894

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_15005.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注