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

前端之家收集整理的这篇文章主要介绍了[HandyJSON] 在Swift语言中处理JSON - 转换JSON和Model前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

背景

JSON是移动端开发常用的应用层数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上。

但在编程的时候,处理JSON是一件麻烦事。在不引入任何轮子的情况下,我们通常需要先把JSON转为Dictionary,然后还要记住每个数据对应的Key,用这个Key在Dictionary中取出对应的Value来使用。这个过程我们会犯各种错误

  • Key拼写错了;
  • 路径写错了;
  • 类型搞错了;
  • 没拿到值懵逼了;
  • 某一天和服务端约定的某个字段变更了,没能更新所有用到它的地方;
  • ...

为了解决这些问题,很多处理JSON的开源库应运而生。在Swift中,这些开源库主要朝着两个方向努力:

  1. 保持JSON语义,直接解析JSON,但通过封装使调用方式更优雅、更安全;
  2. 预定义Model类,将JSON反序列化为类实例,再使用这些实例;

对于1,使用最广、评价最好的库非SwiftyJSON莫属,它很能代表这个方向的核心。它本质上仍然是根据JSON结构去取值,使用起来顺手、清晰。但也正因如此,这种做法没能妥善解决上述的几个问题,因为Key、路径、类型仍然需要开发者去指定;

对于2,我个人觉得这是更合理的方式。由于Model类的存在,JSON的解析和使用都受到了定义的约束,只要客户端和服务端约定好了这个Model类,客户端定义后,在业务中使用数据时就可以享受到语法检查、属性预览、属性补全等好处,而且一旦数据定义变更,编译器会强制所有用到的地方都改过来才能编译通过,非常安全。这个方向上,开源库们做的工作,主要就是把JSON文本反序列化到Model类上了。这一类JSON库有ObjectMapperJSONNeverDieHandyJSON等。而HandyJSON是其中使用最舒服的一个库,本文将介绍用HandyJSON来进行Model和JSON间的互相转换。

为什么用HandyJSON

在Swift中把JSON反序列化到Model类,在HandyJSON出现以前,主要使用两种方式:

  1. 让Model类继承自NSObject,然后class_copyPropertyList()方法获取属性名作为Key,从JSON中取得Value,再通过Objective-C runtime支持KVC机制为类属性赋值;如JSONNeverDie

  2. 支持纯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: var num: Int?
  4.  
  5. required init() {}
  6. }

然后假设我们从服务端拿到这样一个JSON文本:

  1. let jsonString = "{\"name\":\"cat\",\"id\"12345\"num\":180}"

引入HandyJSON以后,我们就可以这样来做反序列化了:

  1. if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) {
  2. print(animal.name)
  3. print(animal.id)
  4. print(animal.num)
  5. }

简单吧~

支持Struct

如果Model的定义是struct,由于Swift中struct提供了默认构造函数,所以就不需要再实现空的init()函数了。但需要注意,如果你为strcut指定了别的构造函数,那么就需要保留一个空的实现。

  1. struct Animal: HandyJSON {
  2. var name: String?
  3. var id: String?
  4. var num: Int?
  5. }
  6.  
  7. let jsonString = "{\"name\":\"cat\",\"id\":\"12345\num\":180}"
  8. if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) {
  9. print(animal)
  10. }

比较复杂的类型

HandyJSON支持在类定义里使用各种形式的基本属性包括可选(?),隐式解包可选(!),数组(Array),字典(Dictionary),Objective-C基本类型(NSString、NSNumber),各种类型的嵌套([Int]?、[String]?、[Int]!、...)等等。比如下面这个看起来比较复杂的类型:

  1. Cat: var id: Int64!
  2. String!
  3. var friend: [String]?
  4. var weight: Double?
  5. var alive: Bool = true
  6. var color: NSString?
  7.  
  8. required init() {}
  9. }

一样轻松转换:

  1. let jsonString = "{\"id\":1234567,\"name\":\"Kitty\"friend\":[\"Tom\"Jack\"Lily\"Black\"],22)">\"weight\":15.34,22)">\"alive\":false,22)">\"color\"white\"}"
  2.  
  3. if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
  4. print(cat.xxx)
  5. }

嵌套的Model类

如果Model类中的某个属性是另一个自定义的Model类,那么只要那个Model类也实现了HandyJSON协议,就一样可以转换:

  1. class Component: HandyJSON {
  2. var aInt: Int?
  3. var aString: String?
  4.  
  5. required init() {}
  6. }
  7.  
  8. class Composition: HandyJSON {
  9. var comp1: Component?
  10. var comp2: Component?
  11.  
  12. required init() {}
  13. }
  14.  
  15. let jsonString = "{\"num\":12345,\"comp1\":{\"aInt\":1,\"aString\":\"aaaaa\"},\"comp2\":2,152)">":\"bbbbb\"}}"
  16.  
  17. if let composition = JSONDeserializer<Composition>.deserializeFrom(json: jsonString) {
  18. print(composition)
  19. }

指定反序列化JSON中某个节点

有时候服务端返回给我们的JSON文本包含了大量的状态信息,和Model无关,比如statusCodedebugMessage等,或者有用的数据是在某个节点以下,那么我们可以指定反序列化哪个节点:

  1. class Cat: HandyJSON {
  2. var name: String!
  3.  
  4. required init() {}
  5. }
  6.  
  7. // 服务端返回了这个JSON,我们想解析的只有data里的cat
  8. let jsonString = "{\"code\":200,\"msg\":\"success\data\":{\"":{\"id\name\":\"Kitty\"}}}"
  9. // 那么,我们指定解析 "data.cat",通过点来表达路径
  10. if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString,designatedPath: "data.cat") {
  11. print(cat.name)
  12. }

有继承关系的Model类

如果某个Model类继承自另一个Model类,只需要这个父Model类实现HandyJSON协议就可以:

  1. class Animal: HandyJSON {
  2. var id: Int?
  3. var color: String?
  4.  
  5. required init() {}
  6. }
  7.  
  8.  
  9. Cat: Animal {
  10. var name: String?
  11.  
  12. required init() {}
  13. }
  14.  
  15. let jsonString = "{\"id\color\":\"black\"}"
  16. Cat>.deserializeFrom(json: jsonString) {
  17. cat)
  18. }

自定义解析方式

HandyJSON还提供了一个扩展能力,就是允许自行定义Model类某个字段的解析Key、解析方式。我们经常会有这样的需求:

  • 某个Model中,我们不想使用和服务端约定的key作为属性名,想自己定一个;
  • 有些类型如enumtuple是无法直接从JSON中解析出来的,但我们在Model类中有这样的属性

HandyJSON协议提供了一个可选的mapping()函数,我们可以在其中指定某个字段用什么Key、或者用什么方法从JSON中解析出它的值。如我们有一个Model类和一个服务端返回的JSON串:

  1. var name: String!
  2. var parent: (String,String)?
  3.  
  4. required init() {}
  5. }
  6.  
  7. let jsonString = "{\"cat_id\parent\":\"Tom/Lily\"}"

可以看到,Cat类的id属性和JSON文本中的Key是对应不上的;而对于parent这个属性来说,它是一个元组,做不到从JSON中的"Tom/Lily"解析出来。所以我们要定义一个Mapping函数来做这两个支持

  1. class Cat: HandyJSON {
  2. var id: Int64!
  3. name: String!
  4. parent: (String,String)?
  5.  
  6. required init() {}
  7.  
  8. func mapping(mapper: HelpingMapper) {
  9. // 指定 id 字段用 "cat_id" 去解析
  10. mapper.specify(property: &id,name: "cat_id")
  11.  
  12. // 指定 parent 字段用这个方法去解析
  13. mapper.specify(: &parent) { (rawString) -> (String) in
  14. let parentNames = rawString.characters.split{$0 == "/"}.map(String.init)
  15. return (parentNames[0],parentNames[1])
  16. }
  17. }
  18. }

就这样,HandyJSON完美地帮我们进行了JSON到Model类的转换。真是太Handy了。

把Model转换为JSON文本

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

基本类型

如果只需要进行序列化,那么在定义Model类时,不需要做任何特殊的改动。任何一个类的实例,直接调用HandyJSON的序列化方法去序列化,就能得到JSON字符串了。

  1. class Animal {
  2. var name: String?
  3. var height: Int?
  4.  
  5. init(name: String,height: Int) {
  6. self.name = name
  7. self.height = height
  8. }
  9. }
  10.  
  11. let cat = Animal(name: "cat",0)">t: 30)
  12.  
  13. // 序列化为简单JSON文本
  14. if let jsonStr = JSONSerializer.serialize(model: cat).toJSON() {
  15. print("simple json string: ",jsonStr)
  16. }
  17.  
  18. // 序列化为格式化的JSON文本
  19. let prettifyJSON = JSONSerializer.serialize(cat).toPrettifyJSON() {
  20. "prettify json string: ",prettifyJSON)
  21. }
  22.  
  23. // 序列化为简单字典
  24. let dict = JSONSerializer.serialize(cat).toSimpleDictionary() {
  25. "dictionary: ",dict)
  26. }

复杂类型

即使Model类中有别的Model类啥的,都一样支持

  1. enum Gender {
  2. case Male
  3. case Female
  4. }
  5.  
  6. struct Subject {
  7. Int64?
  8. String?
  9.  
  10. init( Int64,210)">String) {
  11. self.id = id
  12. self.name = name
  13. }
  14. }
  15.  
  16. class Student {
  17. String?
  18. gender: Gender?
  19. subjects: [Subject]?
  20. }
  21.  
  22. let student = Student()
  23. student.name = "Jack"
  24. student.gender = .Female
  25. student.subjects = [Subject( 1,name: "math"),Subject( 2,152)">"English"),152)"> 3,152)">"Philosophy")]
  26.  
  27. let jsonStr = JSONSerializer.serialize(model: student).toJSON() {
  28. print(let prettifyJSON = JSONSerializer.serialize(model: student).toPrettifyJSON() {
  29. print(let dict = JSONSerializer.serialize(model: student).toSimpleDictionary() {
  30. print( 总结
  31. 有了HandyJSON支持,现在我们可以开心地在Swift中使用JSON了。这个库还在更新中,已经支持Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建议,快去https://github.com/alibaba/handyjson给作者(哈哈没错就是我)提issue吧~~

猜你在找的Swift相关文章