阿里巴巴最新开源项目 - [HandyJSON] 在Swift中优雅地处理JSON
项目名称:HandyJSON
项目地址:https://github.com/alibaba/handyjson
背景
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
类:
class Animal: HandyJSON {
var name: String?
var id: String?
var num: Int? required init() {} // 如果定义是struct,连init()函数都不用声明;
}
然后假设我们从服务端拿到这样一个JSON文本:
let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}"
引入HandyJSON
以后,我们就可以这样来做反序列化了:
if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) {
print(animal.name)
print(animal.id)
print(animal.num)
}
简单吧~
比较复杂的类型
HandyJSON
支持在类定义里使用各种形式的基本属性,包括可选(?),隐式解包可选(!),数组(Array),字典(Dictionary),Objective-C基本类型(NSString、NSNumber),各种类型的嵌套([Int]?、[String]?、[Int]!、...)等等。比如下面这个看起来比较复杂的类型:
struct Cat: HandyJSON {
var id: Int64!
var name: String!
var friend: [String]?
var weight: Double?
var alive: Bool = true
var color: NSString?
}
一样轻松转换:
let jsonString = "{\"id\":1234567,\"name\":\"Kitty\",\"friend\":[\"Tom\",\"Jack\",\"Lily\",\"Black\"],\"weight\":15.34,\"alive\":false,\"color\":\"white\"}" if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
print(cat.xxx)
}
嵌套的Model类
如果Model类中的某个属性是另一个自定义的Model类,那么只要那个Model类也实现了HandyJSON
协议,就一样可以转换:
struct Component: HandyJSON {
var aInt: Int?
var aString: String?
} struct Composition: HandyJSON {
var aInt: Int?
var comp1: Component?
var comp2: Component?
} let jsonString = "{\"num\":12345,\"comp1\":{\"aInt\":1,\"aString\":\"aaaaa\"},\"comp2\":{\"aInt\":2,\"aString\":\"bbbbb\"}}" if let composition = JSONDeserializer<Composition>.deserializeFrom(json: jsonString) {
print(composition)
}
指定反序列化JSON中某个节点
有时候服务端返回给我们的JSON文本包含了大量的状态信息,和Model无关,比如statusCode
,debugMessage
等,或者有用的数据是在某个节点以下,那么我们可以指定反序列化哪个节点:
struct Cat: HandyJSON {
var id: Int64!
var name: String!
} // 服务端返回了这个JSON,我们想解析的只有data里的cat
let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}" // 那么,我们指定解析 "data.cat",通过点来表达路径
if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString, designatedPath: "data.cat") {
print(cat.name)
}
有继承关系的Model类
如果某个Model类继承自另一个Model
类,只需要这个父Model类实现HandyJSON
协议就可以:
class Animal: HandyJSON {
var id: Int?
var color: String? required init() {}
} class Cat: Animal {
var name: String? required init() {}
} let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}" if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
print(cat)
}
自定义解析方式
HandyJSON
还提供了一个扩展能力,就是允许自行定义Model类某个字段的解析Key、解析方式。我们经常会有这样的需求:
- 某个Model中,我们不想使用和服务端约定的key作为属性名,想自己定一个;
- 有些类型如
enum
、tuple
是无法直接从JSON中解析出来的,但我们在Model类中有这样的属性;
HandyJSON
协议提供了一个可选的mapping()
函数,我们可以在其中指定某个字段用什么Key、或者用什么方法从JSON中解析出它的值。如我们有一个Model类和一个服务端返回的JSON串:
class Cat: HandyJSON {
var id: Int64!
var name: String!
var parent: (String, String)? required init() {}
} let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\"}"
可以看到,Cat
类的id
属性和JSON文本中的Key是对应不上的;而对于parent
这个属性来说,它是一个元组,做不到从JSON中的"Tom/Lily"
解析出来。所以我们要定义一个Mapping
函数来做这两个支持:
class Cat: HandyJSON {
var id: Int64!
var name: String!
var parent: (String, String)? required init() {} func mapping(mapper: HelpingMapper) {
// 指定 id 字段用 "cat_id" 去解析
mapper.specify(property: &id, name: "cat_id") // 指定 parent 字段用这个方法去解析
mapper.specify(property: &parent) { (rawString) -> (String, String) in
let parentNames = rawString.characters.split{$0 == "/"}.map(String.init)
return (parentNames[0], parentNames[1])
}
}
}
就这样,HandyJSON
完美地帮我们进行了JSON到Model类的转换。真是太Handy了。
把Model转换为JSON文本
HandyJSON
还提供了把Model类序列化为JSON文本的能力,简直无情。
基本类型
如果只需要进行序列化,那么在定义Model类时,不需要做任何特殊的改动。任何一个类的实例,直接调用HandyJSON
的序列化方法去序列化,就能得到JSON字符串了。
class Animal {
var name: String?
var height: Int? init(name: String, height: Int) {
self.name = name
self.height = height
}
} let cat = Animal(name: "cat", height: 30)
print(JSONSerializer.serializeToJSON(object: cat)!)
print(JSONSerializer.serializeToJSON(object: cat, prettify: true)!)
可以通过prettify
参数来指定获得的是否是格式化后的JSON串。
复杂类型
即使Model类中有别的Model类啥的,都一样支持。
enum Gender: String {
case Male = "male"
case Female = "Female"
} struct Subject {
var id: Int64?
var name: String? init(id: Int64, name: String) {
self.id = id
self.name = name
}
} class Student {
var name: String?
var gender: Gender?
var subjects: [Subject]?
} let student = Student()
student.name = "Jack"
student.gender = .Female
student.subjects = [Subject(id: 1, name: "math"), Subject(id: 2, name: "English"), Subject(id: 3, name: "Philosophy")] print(JSONSerializer.serializeToJSON(object: student)!)
print(JSONSerializer.serializeToJSON(object: student, prettify: true)!)
总结
有了HandyJSON
的支持,现在我们可以开心地在Swift中使用JSON了。这个库还在更新中,已经支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建议,快去 https://github.com/alibaba/handyjson 给作者提issue吧~~
阿里巴巴最新开源项目 - [HandyJSON] 在Swift中优雅地处理JSON的更多相关文章
- 如何在 Swift 中优雅地处理 JSON
阅读目录 在Swift中使用JSON的问题 开始 基础用法 枚举(Enumeration) 下标(Subscripts) 打印 调试与错误处理 后记 因为Swift对于类型有非常严格的控制,它在处 ...
- Druid——阿里巴巴的开源项目(关于数据库连接、监控)
相关文章 0.Druid首页——jdbc连接池,监控组件 1.阿里巴巴的开源项目Druid(关于数据库连接) 2.ITeye谈Druid 3.DBCP(DataBase connection pool ...
- 转发 ----> 2018年阿里巴巴重要开源项目汇总(持续更新中)
转发自segmentfault https://segmentfault.com/a/1190000017346799 前端 1.数据驱动的高交互可视化图形语法 AntV - G2 G2 是一套基于 ...
- iOS: 在Swift中优雅的实现Substring
在Swift中,当我们想要截取某个字符串时,方法如下: let carNumber = "沪A12345" let startIndex = advance(userCar.car ...
- 个人开源项目testall 持续更新中···
项目在GitHub上:https://github.com/x113773/testall ,喜欢的给个星星呀,亲~ 打算把用到过的和学习过的,所有前后端技术都集成到这个项目里,并在issues里配以 ...
- 阿里巴巴的开源项目Druid(关于数据库连接)
1 配置 和dbcp类似,druid的常用配置项如下 配置 缺省值 说明 name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来.如果没有配置,将会生成一个名字,格 ...
- 阿里巴巴开源项目汇总-(JAVA)
来源:https://segmentfault.com/a/1190000017346799 1.分布式应用服务开发的一站式解决方案 Spring Cloud Alibaba Spring Cloud ...
- 【Android 应用开发】GitHub 优秀的 Android 开源项目
原文地址为http://www.trinea.cn/android/android-open-source-projects-view/,作者Trinea 主要介绍那些不错个性化的View,包括Lis ...
- 2016年GitHub排名前20的Python机器学习开源项目(转)
当今时代,开源是创新和技术快速发展的核心.本文来自 KDnuggets 的年度盘点,介绍了 2016 年排名前 20 的 Python 机器学习开源项目,在介绍的同时也会做一些有趣的分析以及谈一谈它们 ...
随机推荐
- jQuery学习之路(2)-DOM操作
▓▓▓▓▓▓ 大致介绍 jQuery作为JavaScript库,继承并发扬了JavaScript对DOM对象操作的特性,使开发人员能方便的操作DOM对象. ▓▓▓▓▓▓ jQuery中的DOM操作 看 ...
- 协议森林16 小美的桌号(DHCP协议)
作者:Vamei 出处:http://www.cnblogs.com/vamei 转载请先与我联系. DHCP协议用于动态的配置电脑的网络相关参数,如主机的IP地址,路由器出口地址.DNS域名服务器地 ...
- 谈谈一些有趣的CSS题目(一)-- 左边竖条的实现方法
开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉 ...
- iOS开发之再探多线程编程:Grand Central Dispatch详解
Swift3.0相关代码已在github上更新.之前关于iOS开发多线程的内容发布过一篇博客,其中介绍了NSThread.操作队列以及GCD,介绍的不够深入.今天就以GCD为主题来全面的总结一下GCD ...
- Android 5.0 到 Android 6.0 + 的深坑之一 之 .so 动态库的适配
(原创:http://www.cnblogs.com/linguanh) 目录: 前序 一,问题描述 二,为何会如此"无情"? 三,目前存在该问题的知名SDK 四,解决方案,1 对 ...
- bzoj3208--记忆化搜索
题目大意: 花花山峰峦起伏,峰顶常年被雪,Memphis打算帮花花山风景区的人员开发一个滑雪项目. 我们可以把风景区看作一个n*n的地图,每个点有它的初始高度,滑雪只能从高处往低处滑[严格大于] ...
- [转载]SQL Server 2008 R2安装时选择的是windows身份验证,未选择混合身份验证的解决办法
安装过程中,SQL Server 数据库引擎设置为 Windows 身份验证模式或 SQL Server 和 Windows 身份验证模式.本文介绍如何在安装后更改安全模式. 如果在安装过程中选择&q ...
- 使用Nginx反向代理 让IIS和Tomcat等多个站点一起飞
使用Nginx 让IIS和Tomcat等多个站点一起飞 前言: 养成一个好习惯,解决一个什么问题之后就记下来,毕竟“好记性不如烂笔头”. 这样也能帮助更多的人 不是吗? 最近闲着没事儿瞎搞,自己在写一 ...
- 使用Xamarin开发iOS7应用时隐藏StatusBar方法
在iOS7之前如果需要隐藏StatusBar,比较简单,直接在AppDelegate.cs中使用如下代码就可以进行隐藏: UIApplication.SharedApplication.StatusB ...
- DotNet Run 命令介绍
前言 本篇主要介绍 asp.net core 中,使用 dotnet tools 运行 dotnet run 之后的系统执行过程. 如果你觉得对你有帮助的话,不妨点个[推荐]. 目录 dotnet r ...