技术控

    今日:86| 主题:49471
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] 在Swift语言中处理JSON - 转换JSON和Model

[复制链接]
寂寞好了 发表于 2016-10-10 11:00:56
91 2

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
背景

  JSON是移动端开发常用的应用层数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上。
  但在编程的时候,处理JSON是一件麻烦事。在不引入任何轮子的情况下,我们通常需要先把JSON转为Dictionary,然后还要记住每个数据对应的Key,用这个Key在Dictionary中取出对应的Value来使用。这个过程我们会犯各种错误:
  
       
  •       Key拼写错了;
       
  •       路径写错了;
       
  •       类型搞错了;
       
  •       没拿到值懵逼了;
       
  •       某一天和服务端约定的某个字段变更了,没能更新所有用到它的地方;
       
  •       ...
      
  为了解决这些问题,很多处理JSON的开源库应运而生。在Swift中,这些开源库主要朝着两个方向努力:
  
       
  •       保持JSON语义,直接解析JSON,但通过封装使调用方式更优雅、更安全;
       
  •       预定义Model类,将JSON反序列化为类实例,再使用这些实例;
      
  对于1,使用最广、评价最好的库非    SwiftyJSON莫属,它很能代表这个方向的核心。它本质上仍然是根据JSON结构去取值,使用起来顺手、清晰。但也正因如此,这种做法没能妥善解决上述的几个问题,因为Key、路径、类型仍然需要开发者去指定;  
  对于2,我个人觉得这是更合理的方式。由于Model类的存在,JSON的解析和使用都受到了定义的约束,只要客户端和服务端约定好了这个Model类,客户端定义后,在业务中使用数据时就可以享受到语法检查、属性预览、属性补全等好处,而且一旦数据定义变更,编译器会强制所有用到的地方都改过来才能编译通过,非常安全。这个方向上,开源库们做的工作,主要就是把JSON文本反序列化到Model类上了。这一类JSON库有    ObjectMapper、    JSONNeverDie、    HandyJSON等。而    HandyJSON是其中使用最舒服的一个库,本文将介绍用      HandyJSON来进行Model和JSON间的互相转换。  
  为什么用HandyJSON

  在Swift中把JSON反序列化到Model类,在    HandyJSON出现以前,主要使用两种方式:  
  
       
  •       让Model类继承自        NSObject,然后        class_copyPropertyList()方法获取属性名作为Key,从JSON中取得Value,再通过        Objective-C runtime支持的        KVC机制为类属性赋值;如        JSONNeverDie;      
       
  •       支持纯Swift类,但要求开发者实现        Mapping函数,使用重载的运算符进行赋值,如        ObjectMapper;      
      
  这两者都有显而易见的缺点。前者要求Model继承自    NSObject,非常不优雅,且直接否定了用    struct来定义Model的方式;后者的    Mapping函数要求开发者自定义,在其中指明每个属性对应的JSON字段名,代码侵入大,且仍然容易发生拼写错误、维护困难等问题。  
  而    HandyJSON独辟蹊径,采用    Swift反射+    内存赋值的方式来构造Model实例,规避了上述两个方案遇到的问题。  
  把JSON转换为Model

  简单类型

  某个Model类想支持通过    HandyJSON来反序列化,只需要在定义时,实现    HandyJSON协议,这个协议只要求实现一个空的    init()函数。  
  比如我们和服务端约定了一个    Animal数据,里面有    name/    id/    num字段,那么我们这样定义    Animal类:  
  1. class Animal: HandyJSON {
  2.     var name: String?
  3.     var id: String?
  4.     var num: Int?
  5.     required init() {} // 如果定义是struct,连init()函数都不用声明;
  6. }
复制代码
然后假设我们从服务端拿到这样一个JSON文本:
  1. let jsonString = "{"name":"cat","id":"12345","num":180}"
复制代码
引入    HandyJSON以后,我们就可以这样来做反序列化了:  
  1. if let animal = JSONDeserializer
  2. .deserializeFrom(json: jsonString) {
  3.     print(animal.name)
  4.     print(animal.id)
  5.     print(animal.num)
  6. }
复制代码
简单吧~
  比较复杂的类型

      HandyJSON支持在类定义里使用各种形式的基本属性,包括可选(?),隐式解包可选(!),数组(Array),字典(Dictionary),Objective-C基本类型(NSString、NSNumber),各种类型的嵌套([Int]?、[String]?、[Int]!、...)等等。比如下面这个看起来比较复杂的类型:  
  1. struct Cat: HandyJSON {
  2.     var id: Int64!
  3.     var name: String!
  4.     var friend: [String]?
  5.     var weight: Double?
  6.     var alive: Bool = true
  7.     var color: NSString?
  8. }
复制代码
一样轻松转换:
  1. let jsonString = "{"id":1234567,"name":"Kitty","friend":["Tom","Jack","Lily","Black"],"weight":15.34,"alive":false,"color":"white"}"
  2. if let cat = JSONDeserializer
  3. .deserializeFrom(json: jsonString) {
  4.     print(cat.xxx)
  5. }
复制代码
嵌套的Model类

  如果Model类中的某个属性是另一个自定义的Model类,那么只要那个Model类也实现了    HandyJSON协议,就一样可以转换:  
  1. struct Component: HandyJSON {
  2.     var aInt: Int?
  3.     var aString: String?
  4. }
  5. struct Composition: HandyJSON {
  6.     var aInt: Int?
  7.     var comp1: Component?
  8.     var comp2: Component?
  9. }
  10. let jsonString = "{"num":12345,"comp1":{"aInt":1,"aString":"aaaaa"},"comp2":{"aInt":2,"aString":"bbbbb"}}"
  11. if let composition = JSONDeserializer
  12. .deserializeFrom(json: jsonString) {
  13.     print(composition)
  14. }
复制代码
指定反序列化JSON中某个节点

  有时候服务端返回给我们的JSON文本包含了大量的状态信息,和Model无关,比如    statusCode,    debugMessage等,或者有用的数据是在某个节点以下,那么我们可以指定反序列化哪个节点:  
  1. struct Cat: HandyJSON {
  2.     var id: Int64!
  3.     var name: String!
  4. }
  5. // 服务端返回了这个JSON,我们想解析的只有data里的cat
  6. let jsonString = "{"code":200,"msg":"success","data":{"cat":{"id":12345,"name":"Kitty"}}}"
  7. // 那么,我们指定解析 "data.cat",通过点来表达路径
  8. if let cat = JSONDeserializer
  9. .deserializeFrom(json: jsonString, designatedPath: "data.cat") {
  10.     print(cat.name)
  11. }
复制代码
有继承关系的Model类

  如果某个Model类继承自另一个    Model类,只需要这个父Model类实现    HandyJSON协议就可以:  
  1. class Animal: HandyJSON {
  2.     var id: Int?
  3.     var color: String?
  4.     required init() {}
  5. }
  6. class Cat: Animal {
  7.     var name: String?
  8.     required init() {}
  9. }
  10. let jsonString = "{"id":12345,"color":"black","name":"cat"}"
  11. if let cat = JSONDeserializer
  12. .deserializeFrom(json: jsonString) {
  13.     print(cat)
  14. }
复制代码
自定义解析方式

      HandyJSON还提供了一个扩展能力,就是允许自行定义Model类某个字段的解析Key、解析方式。我们经常会有这样的需求:  
  
       
  •       某个Model中,我们不想使用和服务端约定的key作为属性名,想自己定一个;
       
  •       有些类型如        enum、        tuple是无法直接从JSON中解析出来的,但我们在Model类中有这样的属性;      
      
      HandyJSON协议提供了一个可选的    mapping()函数,我们可以在其中指定某个字段用什么Key、或者用什么方法从JSON中解析出它的值。如我们有一个Model类和一个服务端返回的JSON串:  
  1. class Cat: HandyJSON {
  2.     var id: Int64!
  3.     var name: String!
  4.     var parent: (String, String)?
  5.     required init() {}
  6. }
  7. let jsonString = "{"cat_id":12345,"name":"Kitty","parent":"Tom/Lily"}"
复制代码
可以看到,    Cat类的    id属性和JSON文本中的Key是对应不上的;而对于    parent这个属性来说,它是一个元组,做不到从JSON中的    "Tom/Lily"解析出来。所以我们要定义一个    Mapping函数来做这两个支持:  
  1. class Cat: HandyJSON {
  2.     var id: Int64!
  3.     var name: String!
  4.     var parent: (String, String)?
  5.     required init() {}
  6.     func mapping(mapper: HelpingMapper) {
  7.         // 指定 id 字段用 "cat_id" 去解析
  8.         mapper.specify(property: &id, name: "cat_id")
  9.         // 指定 parent 字段用这个方法去解析
  10.         mapper.specify(property: &parent) { (rawString) -> (String, String) in
  11.             let parentNames = rawString.characters.split{$0 == "/"}.map(String.init)
  12.             return (parentNames[0], parentNames[1])
  13.         }
  14.     }
  15. }
复制代码
就这样,    HandyJSON完美地帮我们进行了JSON到Model类的转换。真是太Handy了。  
  把Model转换为JSON文本

      HandyJSON还提供了把Model类序列化为JSON文本的能力,简直无情。  
  基本类型

  如果只需要进行序列化,那么在定义Model类时,不需要做任何特殊的改动。任何一个类的实例,直接调用    HandyJSON的序列化方法去序列化,就能得到JSON字符串了。  
  1. let jsonString = "{"name":"cat","id":"12345","num":180}"0
复制代码
可以通过    prettify参数来指定获得的是否是格式化后的JSON串。  
  复杂类型

  即使Model类中有别的Model类啥的,都一样支持。
  1. let jsonString = "{"name":"cat","id":"12345","num":180}"1
复制代码
总结

  有了    HandyJSON的支持,现在我们可以开心地在Swift中使用JSON了。这个库还在更新中,已经支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建议,快去    https://github.com/alibaba/handyjson给作者(哈哈没错就是我)提issue吧~~
友荐云推荐




上一篇:深入理解JavaScript中的this关键字
下一篇:揭秘!这才是比BAOCMS更强的本地生活门户O2O系统!
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

gutou001 发表于 2016-10-10 15:02:47
顶一下,收藏了!
回复 支持 反对

使用道具 举报

gogella 发表于 2016-10-31 22:03:00
很给力!
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表