Swift Json实例详细解析
前言 客户端开发项目中,不可避免地需要解析网络数据---将服务端下发的JSON数据解析成客户端可阅读友好的Model。Objective-C下使用最多的是JSONModel,它能在OC Runtime基础下很好地完成解析工作。那么在纯Swift代码中,这个功能是如何实现的?下面开始我们的探索~
手动解析 假设一个User类要解析,Json如下: { "userId": 1,"name": "Jack","height": 1.7,} 对应的创建一个User结构体(也可以是类): struct User { var userId: Int? var name: String? var height: CGFloat? } 把JSON转成User 在Swift4.0前,我们以手动解析的方式将JSON model化。给User加一个以JSON为参数的初始化方法,代码如下: struct User { ... init?(json: [String: Any]) { guard let userId = json["userId"] as? Int,let name = json["name"] as? String,let height = json["height"] as? CGFloat else { return nil } self.userId = userId self.name = name self.height = height } } 依次从json中取出model所需的具体类型的数据,填充到具体对应属性中。如果其中一个转换失败或者没有值,初始化会失败返回nil。 如果某个值不需要强校验,直接取值再赋值,把guard let内的语句去掉。例如,若height不用校验,可看如下代码: struct User { ... init?(json: [String: Any]) { guard let userId = json["userId"] as? Int,let name = json["name"] as? String else { return nil } self.userId = userId self.name = name self.height = json["height"] as? CGFloat } } 原生:Swift4.0 JSONDecoder 2017年6月份左右Swift4.0发布,其中一个重大更新就是JSON的加解密。摆脱手工解析字段的繁琐,聊聊几行代码就可将JSON转换成Model。与Objective-C下的JSONModel极为相似。同样解析上述例子中的User,Swift4.0可以这么写: struct User: Decodable { var userId: Int? var name: String? var height: CGFloat? } let decoder = JSONDecoder() if let data = jsonString.data(using: String.Encoding.utf8) { let user = try? decoder.decode(User.self,from: data) } so easy~ 与手动解析不同点在于: 1.移除了手写init?方法。不需要手动解了 2.User实现Decodable协议,协议的定义如下: /// A type that can decode itself from an external representation. public protocol Decodable { /// Creates a new instance by decoding from the given decoder. /// /// This initializer throws an error if reading from the decoder fails,or /// if the data read is corrupted or otherwise invalid. /// /// - Parameter decoder: The decoder to read data from. public init(from decoder: Decoder) throws } Decodable协议只有一个方法 3.使用了JSONDecoder。它是真正的解析工具,主导整个解析过程 读到这里,是不是觉得人生从黑暗迈向了光明~~ 可是,它并不完美... JSONDecoder问题及方案 解析JSON经常遇到这样两种不一致问题:
前两个问题JSONDecoder都能很好地解决。 第一个key不一致问题,JSONDecoder有现成的方案。以上面介绍的例子来说,假设服务端返回的key是user_id而不是userId,那么我们可以使用JSONDecoder的CodingKeys像JSONModel一样对属性名称在加解密时的名称做转换。User修改如下: struct User: Decodable { var userId: Int? var name: String? var height: CGFloat? enum CodingKeys: String,CodingKey { case userId = "user_id" case name case height } } 第二个,Date转换问题。JSONDecoder也为我们提供了单独的API: open class JSONDecoder { /// The strategy to use for decoding `Date` values. public enum DateDecodingStrategy { /// Defer to `Date` for decoding. This is the default strategy. case deferredToDate /// Decode the `Date` as a UNIX timestamp from a JSON number. case secondsSince1970 /// Decode the `Date` as UNIX millisecond timestamp from a JSON number. case millisecondsSince1970 /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). case iso8601 /// Decode the `Date` as a string parsed by the given formatter. case formatted(DateFormatter) /// Decode the `Date` as a custom value decoded by the given closure. case custom((Decoder) throws -> Date) } ...... /// The strategy to use in decoding dates. Defaults to `.deferredToDate`. open var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy } 设置好了JSONDecoder属性dateDecodingStrategy后,解析Date类型就会按照指定的策略进行解析。 类型不一致 至此,JSONDecoder为我们提供了
但遇到基本类型端上与服务端不一致时(比如一个数字1,端上的Code是Int型,服务端下发String:"1"),JSONDecoder会抛出typeMismatch异常而终结整个数据的解析。 这让人有点懊恼,端上的应用,我们希望它能够尽可能稳定,而不是某些情况下遇到若干个基本类型不一致整个解析就停止,甚至是 Crash。 如下面表格所示,我们希望类型不匹配时,能够这么处理:左列代表前端的类型,右列代表服务端类型,每一行代表前端类型为X时,能从服务端下发的哪些类型中转化,比如String 可以从 IntorFloat转化。这几个类型基本能覆盖日常服务端下发的数据,其它类型的转化可根据自己的需求扩充。
JSONDecoder没有给我们便利的这种异常处理的API。如何解决呢?最直接的想法,在具体的model内实现init(decoder: Decoder)手动解析可以实现,但每个都这么处理太麻烦。 解决方案:KeyedDecodingContainer方法覆盖 研究JSONDecoder的源码,在解析自定义Model过程中,会发现这样一个调用关系。 // 入口方法 JSONDecoder decoder(type:Type data:Data) // 内部类,真实用来解析的 _JSONDecoder unbox(value:Any type:Type) // Model调用init方法 Decodable init(decoder: Decoder) // 自动生成的init方法调用container Decoder container(keyedBy:CodingKeys) // 解析的容器 KeyedDecodingContainer decoderIfPresent(type:Type) or decode(type:Type) // 内部类,循环调用unbox _JSONDecoder unbox(value:Any type:Type) ...循环,直到基本类型 最终的解析落到,_JSONDecoder的unbox 及 KeyedDecodingContainer的decoderIfPresent decode方法。但_JSONDecoder是内部类,我们处理不了。最终决定对KeyedDecodingContainer下手,其中部分代码如下: extension KeyedDecodingContainer { ....... /// Decode (Int,String) -> Int if possiable public func decodeIfPresent(_ type: Int.Type,forKey key: K) throws -> Int? { if let value = try? decode(type,forKey: key) { return value } if let value = try? decode(String.self,forKey: key) { return Int(value) } return nil } ....... /// Avoid the failure just when decoding type of Dictionary,Array,SubModel failed public func decodeIfPresent<T>(_ type: T.Type,forKey key: K) throws -> T? where T : Decodable { return try? decode(type,forKey: key) } } 上述代码中,第一个函数 为什么要写第二个函数呢? 场景:当我们Model内有其他的非基本类型的Model,比如其他自定义Model,Dictionary<String,Any>,Array<String>等,当这些Model 类型不匹配或者出错误时也会抛出异常,导致整个大Model解析失败。 为何不覆盖decode方法?decodeIfPresent可以返回Optional值,decode返回确定类型值。考虑到如果Model内如果定义的类型是No-Optional型,那么可以认为开发者确定该值必须存在,如果不存在Model很可能是错误的,所以直接fail。 完整扩展代码点我 (本地下载点我) 总结 Swift4.0 JSONDecoder确实为解析数据带来了极大的便利。使用方式上类似Objective-C下的JSONModel。但实际开发中还是需要一些改造才能更好地服务于我们。 好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。 您可能感兴趣的文章:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |