阅读 946

Swift:JSON解析(上)

这是我参与更文挑战的第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会引用不少例子。

明日继续吧,大家加油!

文章分类
iOS
文章标签