今天介绍一个最新Xocde9后,Foundation中更新的一个比较实用及有意思的Decoding功能。
在和网络交互的过程中,我们通过网络请求获得到的响应数据绝大多数都是json格式的数据,而我们通常会将获得的json数据转换成对应的Model模型数据,从而方便我们使用,而这个从json到Model的过程还是有点小麻烦的,一般我们我们可以手动自己解析,也可以通过三方库去解析,方式还是很多的。 在objective-c中,由于OC不是强类型的语言,所以在我们自定义的Model和json数据之间,对具体字段的类型不会有特别严格的类型要求,比如json中的不确定的数据类型的字段,我们可以在Model中用NSString类型的字段去接受,然后通过setValuesForKeysWithDictionary()
去赋值,只需要在具体使用的时候去注意就好了。
但是在Swift中这个办法行不通了,因为Swift是强类型的语言,这就造成了,在Swift中如果json数据中某个字段的类型和我们自定义的Model中的字段类型不匹配,同样的用setValuesForKeysWithDictionary()
方法去解析json数据就会造成崩溃,如果接口不靠谱,则会因为字段类型不匹配造成很大的麻烦。现在苹果官方给大家提供了一个非常方便实用的方式来解析json数据,具体如下:(终于来到关键了...) 现在我们有一段json数据如下:
{ "status":"ok", "sources":[ { "id":"abc-news-au", "name":"ABC News (AU)", "description": "Australia's most trusted source of local, national and world news. Comprehensive, independent, in-depth analysis, the latest business, sport, weather and more.", "url": "http://www.abc.net.au/news", "category": "general", "language": "en", "country": "au", "urlsToLogos": { "small":"", "medium":"", "large":"" }, "sortBysAvailable": ["top"] }, { .... }复制代码
从这段json格式数据我们可以看到,最外层是一个字典,sources字段是一个包含字典的数组,现在我们需要获得sources字段中的数据
- 1 创建名为SourceModel的模型类,给这个类中添加我们需要的对应json数据中的属性,通常为了通过
setValuesForKeysWithDictionary()
来获得json中的数据,我们需要将模型中的属性名字和json数据中字段名字1对1对应
class SourceModel:NSObject { let id: String let name: String let category: String let description: String init(_ id: String, name: String, category: String, description: String) { self.id = id self.name = name self.category = category self.overview = description }}复制代码
通常来说我们的模型基本创建完成(由于后面我要对这个属性用到KVO所以要继承自NSObject),现在为了能够用我们的Decoding新魔法,我们对模型进行初步改造,改造后如下:
//1class SourceModel:NSObject, Codable { let id: String let name: String let category: String let overview: String //2 enum CodingKeys: String, CodingKey { case id case name case category //3 case overview = "description" } //4 init(_ id: String, name: String, category: String, overview: String) { self.id = id self.name = name self.category = category self.overview = overview }}复制代码
具体步骤:
1.遵守Codable协议,Codable协议是由Encodable和Decodable两个协议组成的,遵守这个协议后,编译器会自动帮我们生成必要的Encodable和Decodable方法
2.由于模型继承自NSObject,而 NSObject中存在名为description的属性,所以子类中不能再有同样的属性,这里我们需要将description进行更名overview(也可能就任性的想改个顺眼的名字),现在你想要用overview去匹配json中的description,所以你要实现//2中的方法,同时做//3样的更改,这是告诉编译器你想要更改字段名字的诉求,如果你不提供这个方法,系统会自动生成这个方法,如果你实现了这个方法,系统会访问你的方法,并按照其中的规则来匹配对应的字段。
注意://2处提供的方法,一定要严格按照这个格式要求来
- 2接着我们可以创建一个临时的中间数据结构来方便处理数据(后面大家就知道我为什么要这样了)
//中间结构struct Respone: Codable { var sources: [SourceModel]?}复制代码
//解析数据,参数1为你需要提供的数据类型,参数2为网络请求下来的Data数据let respone = try? JSONDecoder().decode(Respone.self, from: data)复制代码
经过上面的一段代码你就会发现神奇的事情放生了,在respone实例的sources中含有已经解析出来的[SourceModel]数据(我代码中提供的应该是72个SourceModel元素组成的数组),到这里json的解析已经完成了。这里Respone结构体中的变量名sources一定要和json数据中的字段名字相对于,如果你想解析json中的status字段,那么你可以在Respone结构体重添加名为status的变量,如果想要改名则和SourceModel中的操作一样。
由于获得的为可选项,同样的我们也可以将上面的代码完善:
guard let sources = try? JSONDecoder().decode(Respone.self, from: data).sources else { return }复制代码
以上详见demo中NewsAPI.swift及SourceModel.swift中的代码
问题及解决办法
在实际解析数据的过程中我们还会碰到其他的问题及需求,这里我把我碰到的想到的列出来给大家参考。
-
1 如果json中含有的字段,而我不需要,怎么处理
答:就像例子中的status字段一样,在你的模型中你不声明,该字段就会自动被忽略
-
2 如果json中某个字段,可能有值也可能没值,怎么处理
答:在模型中将该字段声明为可选类型
-
3 我想对某个字段的数据进行特殊处理怎么办,如我想SourceModel在name字段值的前面加上“名字:”(这个问题,我认为现在的处理方式有一点点麻烦)
答:由于通过Decoding解析数据时,是通过初始化方法创建模型数据的,所以模型中属性的didSet{}方法不被调用,我们不可以在这里对数据进行处理,我们需要在模型数据中的初始化方法中处理这个属性 代码详见ArticleCell.swift
//在初始化方法中处理required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let tempName = try container.decodeIfPresent(String.self, forKey: .name) name = "名字:" + tempName //下面即使不需要重新处理的数据也要这样的重新的写一遍,这是我认为有点不好的地方 id = container.decodeIfPresent(String.self, forKey: .id) }复制代码
- 4 在解析数据时,如果碰见数据类型不匹配,字段不存在等问题时,我们可以用下面的这个保险的方法,将解析处的代码更改如下,这样既方便调试也防止崩溃(实际代码中可以只写一个catch防止崩溃就好,调试的时候可以都写出来),详见代码NewsAPI.swift中:
do { guard let sources = try JSONDecoder().decode(Respone.self, from: data).sources else { return } self.sources = sources }catch DecodingError.keyNotFound(let key, let context){ print("MissKey:\(key)") print("Debug description:\(context)") }catch DecodingError.valueNotFound(let value, let context) { print("MissValue:\(value)") print("Debug description:\(context)") }catch DecodingError.typeMismatch(let type, let context) { print("MissType:\(type)") print("Debug description:\(context)") }catch { print(error.localizedDescription) }复制代码
以上就是本人了解到的Decoding解析Json数据的全部总结了,如果有更多的了解,请及时交流,我也会继续完善的。
最后还要说一个小的知识点,如果你看了,你会发现我用了KVO来监听数据的更新,新的KVO的用法比以前友好及方便了(详见demo中的数据刷新方式)。
ps:这个是本人在掘金的第一篇笔记,以前混简书的,因为大家都知道的原因,以后不准备在简书混了...
地址,欢迎下载