这是我参与更文挑战的第15天,活动详情查看: 更文挑战
前言
但凡需要网络请求,就需要进行数据交换,就避免不了JSON转Model。
JSON: JavaScript Object Notation(JavaScript 对象表示法),由于JSON实际上是JavaScript的产物,所以它对前端最为友好,基本上不存在转换一说,直接通过.
语法即可调用。
我们来看一个简单的例子:
var myObj, x;
myObj = { "name":"runoob", "alexa":10000, "site":null };
x = myObj.name;
复制代码
JavaScript中直接就可以把myObj.name给调用出来,这是其他编程语言做不到的。
就比如Swift中有Dictionary,在Java、Dart、Kotlin中有Map,在Python中有dict,这些不同编程语言的类型其实都是为了对应JSON——键值对的表示方式。
不过我们在使用的时候很少考虑会通过键值对进行大量的赋值或者
引用,为什么?
通过key去获取value的写法——myObj[“name”]这种方式,过于依靠字符串硬编码,一个不留神就手写敲错。
-
书写容易出错
-
排查困难
-
可读写性差
-
可扩展性差
基于以上这些原因,一般情况下,网络请求获取的JSON数据,我们都会进行一次转Model操作。
如何进行JSON解析
不同语言有不一样的回答,Java通过反射,Dart由于考虑性能因素屏蔽了反射,更多的是考虑使用自动化工具进行转换,OC中我使用的是YYModel,而在目前Swift5中,我个人首先推荐的是Swift语言自带的Codable协议进行
为何是Codable协议?
要回答这个问题,可以通过我自己使用Swift2.0到4.0的JSON转Model的体会来说说。
Swift2.0——青铜时代
我大约在16年的时候开始接触Swift语言,从OC到Swift的起步,我学习的方式就是将OC的那一套逻辑直接拿到Swift中里面用,所以我那会转Model依旧考虑使用YYModel。
由于年少无知,那会不知道其实有纯Swift框架的JSON转Model工具。
YYModel是非常出色的OC中JSON转模型工具,但是在Swift中就有些水土不服。
如果想要使用YYModel,所有的模型对象必须继承NSObject,模型中的对象基本上比如使用OC的类型,比如数组使用NSArray,字典使用NSDictionary,字符使用NSString,因为这些都是class类型,而在Swift中Array、Dictionary、String这些基本数据类型都被设计为struct,设计的不同,导致在转Model的过程中使用的思路也不同,如果搞不清楚这些,就会莫名其妙的崩溃或者报错。
好在,Swift2我并没有写过商业级的App,不然肯定会被笑掉大牙吧。
Swift3.0——白银时代
在Swift3的时候,我通过翻阅资料与自己学习研究,基本上确定了两个框架——ObjectMapper和SwiftyJSON。
SwiftyJSON对于JSON的解析其实不算太友好,因为还是不得不通过字符串硬编码进行调用,说它是一个调试工具更为合理。
所以,更多的时候,我考虑使用的ObjectMapper。我们来看下面这个例子:
JSON(为了避免重复写JSON,会都以这个为样例):
{
province : 湖北省,
city : 武汉市,
location : {
lat : 30.60,
lng : 114.04
},
name : XXXXXXX,
address : XXXXXXX,
detail : 1,
area : 汉阳区,
street_id : 3a1fde420cbb9d4dab059d36,
uid : 3a1fde420cbb9d4dab059d36
}
复制代码
这个JSON里面包裹了一次location,我们通过ObjectMapper会这么写:
import ObjectMapper
/// 经纬度模型
struct LatLngEntity: Mappable {
//MARK:- 属性设置
var lat: Double?
var lng: Double?
//MARK:- Mapper协议
init?(map: Map) {
}
mutating func mapping(map: Map) {
lat <- map["lat"]
lng <- map["lng"]
}
}
struct PoiEntity: Mappable {
//MARK:- 属性设置
var province: String?
var city: String?
var location: LatLngEntity?
var name: String?
var address: String?
var detail: String?
var area: String?
var street_id: String?
var uid : String?
init?(map: Map) {
}
mutating func mapping(map: Map) {
province <- map["province"]
city <- map["city"]
location <- map["location"]
name <- map["name"]
address <- map["address"]
detail <- map["detail"]
area <- map["area"]
street_id <- map["street_id"]
uid <- map["uid"]
}
}
复制代码
其大致思路流程如下:
模型类继承Mappable协议,实现协议,在mutating func mapping(map: Map)
方法中做好一一对应的映射关系。
在早期,JSON转Swift语言Model的工具很少的时候,<- map[]
这种编码都是手写的,你可以想想,一旦JSON属性一多,也无异于在硬编码的地狱中。
直到我了解到JSONExport这个工具才有所改善。
另外就是ObjectMapper对于JSON中数组解析,和单个元素的解析需要调用不同的方式,导致了在考虑使用泛型设计BaseModel接数据的会复杂一些。
这段代码中的T(JSON: JSON)
和 Mapper<T>().mapArray(JSONArray: JSONS)
就是对字典和数组字典通过Mapper进行转换
/// 将继承Mappable的模型 字典转模型
///
/// - Parameter dict: 数据字典
/// - Returns: 需要的模型
func mappableWithDict<T: Mappable>(_ dict: [String: Any]?) -> T? {
guard let JSON = dict else {
return nil
}
return T(JSON: JSON)
}
/// 将继承Mappable的模型 字典数组转模型模型数组
///
/// - Parameter dicts: 数据字典数组
/// - Returns: 模型数组
func mappableWithDictArray<T: Mappable>(_ dicts: [[String: Any]]?) -> [T]? {
guard let JSONS = dicts else {
return nil
}
return Mapper<T>().mapArray(JSONArray: JSONS)
}
复制代码
于是模型就必须设计两个:
//MARK:- 泛型装配 通用
class Response<T: Mappable>: Mappable {
var message : String?
var result : T?
var status : Int?
required init?(map: Map) {
}
// 这个地方的字符串可以进行转译映射 这个可控制性更强
func mapping(map: Map) {
message <- map["message"]
result <- map["result"]
status <- map["status"]
}
}
class ResponseArray<T: Mappable>: Mappable {
var message : String?
var result : [T]?
var status : Int?
required init?(map: Map) {
}
func mapping(map: Map) {
message <- map["message"]
result <- map["result"]
status <- map["status"]
}
}
复制代码
一般同一套后台返回的JSON格式都会比较通用,比如这段代码中的message是传递的请求响应信息、status是业务服务码,唯一不同的可能就是这个result,它可能是一个JSON抑或是一个[JSON],而使用两个基本模型去区别这两种情况,使得网络层的封装就变得复杂了起来。
就连ObjectMapper的Alamofire分类,也是使用的两套Api:
public func responseObject<T: BaseMappable>(queue: DispatchQueue? = nil, keyPath: String? = nil, mapToObject object: T? = nil, context: MapContext? = nil, completionHandler: @escaping (DataResponse<T>) -> Void) -> Self
public func responseArray<T: BaseMappable>(queue: DispatchQueue? = nil, keyPath: String? = nil, context: MapContext? = nil, completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self
复制代码
就在我以为,我会在今后的Swift开发中一直使用ObjectMapper作为JSON转Model的工具类时。Swift4,Codable横空出世,打破了整个格局。
明日继续
鉴于文章的篇幅较长,我今天写到这里了,虽然代码不少,但是考虑Codable会引用不少例子。
明日继续吧,大家加油!