[HandyJSON] 在Swift语言中处理JSON - 转换
背景
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
在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实例,规避了上述两个方案遇到的问题,保持原汁原味的Swift类定义。贴一段很能展示这个特点的代码:
// 假设这是服务端返回的统一定义的response格式
class BaseResponse<T: HandyJSON>: HandyJSON {
var code: Int? // 服务端返回码
var data: T? // 具体的data的格式和业务相关,故用泛型定义
public required init() {}
}
// 假设这是某一个业务具体的数据格式定义
struct SampleData: HandyJSON {
var id: Int?
}
let sample = SampleData(id: 2)
let resp = BaseResponse<SampleData>()
resp.code = 200
resp.data = sample
let jsonString = resp.toJSONString()! // 从对象实例转换到JSON字符串
print(jsonString) // print: {"code":200,"data":{"id":2}}
if let mappedObject = JSONDeserializer<BaseResponse<SampleData>>.deserializeFrom(json: jsonString) { // 从字符串转换为对象实例
print(mappedObject.data?.id)
}
如果是继承NSObject
类的话,Model定义是没法用泛型的。
把JSON转换为Model
简单类型
某个Model类想支持通过HandyJSON
来反序列化,只需要在定义时,实现HandyJSON
协议,这个协议只要求实现一个空的init()
函数。
class BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
required init() {}
}
然后假设我们从服务端拿到这样一个JSON文本:
let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}"
引入HandyJSON
以后,我们就可以这样来做反序列化了:
if let object = JSONDeserializer<BasicTypes>.deserializeFrom(json: jsonString) {
// …
}
简单吧~
支持Struct
如果Model的定义是struct,由于Swift中struct提供了默认构造函数,所以就不需要再实现空的init()
函数了。但需要注意,如果你为strcut指定了别的构造函数,那么就需要保留一个空的实现。
struct BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
}
let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}"
if let object = JSONDeserializer<BasicTypes>.deserializeFrom(json: jsonString) {
// …
}
支持枚举
支持值类型的enum,只需要声明服从HandyJSONEnum
协议。不再需要其他特殊处理了。
enum AnimalType: String, HandyJSONEnum {
case Cat = "cat"
case Dog = "dog"
case Bird = "bird"
}
struct Animal: HandyJSON {
var name: String?
var type: AnimalType?
}
let jsonString = "{\"type\":\"cat\",\"name\":\"Tom\"}"
if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) {
print(animal.type?.rawValue)
}
较复杂的类型,如可选、隐式解包可选、集合等
HandyJSON支持这些非基础类型,包括嵌套结构。
class BasicTypes: HandyJSON {
var bool: Bool = true
var intOptional: Int?
var doubleImplicitlyUnwrapped: Double!
var anyObjectOptional: Any?
var arrayInt: Array<Int> = []
var arrayStringOptional: Array<String>?
var setInt: Set<Int>?
var dictAnyObject: Dictionary<String, Any> = [:]
var nsNumber = 2
var nsString: NSString?
required init() {}
}
let object = BasicTypes()
object.intOptional = 1
object.doubleImplicitlyUnwrapped = 1.1
object.anyObjectOptional = "StringValue"
object.arrayInt = [1, 2]
object.arrayStringOptional = ["a", "b"]
object.setInt = [1, 2]
object.dictAnyObject = ["key1": 1, "key2": "stringValue"]
object.nsNumber = 2
object.nsString = "nsStringValue"
let jsonString = object.toJSONString()!
if let object = JSONDeserializer<BasicTypes>.deserializeFrom(json: jsonString) {
// ...
}
指定解析路径
HandyJSON支持指定从哪个具体路径开始解析,反序列化到Model。
class Cat: HandyJSON {
var id: Int64!
var name: String!
required init() {}
}
let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}"
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支持自定义映射关系,或者自定义解析过程。你需要实现一个可选的mapping
函数,在里边实现NSString
值(HandyJSON会把对应的JSON字段转换为NSString)转换为你需要的字段类型。
所以无法直接支持的类型,都可以用这个方式支持。
class Cat: HandyJSON {
var id: Int64!
var name: String!
var parent: (String, String)?
required init() {}
func mapping(mapper: HelpingMapper) {
// specify 'cat_id' field in json map to 'id' property in object
mapper <<<
self.id <-- "cat_id"
// specify 'parent' field in json parse as following to 'parent' property in object
mapper <<<
self.parent <-- TransformOf<(String, String), String>(fromJSON: { (rawString) -> (String, String)? in
if let parentNames = rawString?.characters.split(separator: "/").map(String.init) {
return (parentNames[0], parentNames[1])
}
return nil
}, toJSON: { (tuple) -> String? in
if let _tuple = tuple {
return "\(_tuple.0)/\(_tuple.1)"
}
return nil
})
}
}
let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\"}"
if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
print(cat.id)
print(cat.parent)
}
排除指定属性
如果在Model中存在因为某些原因不能实现HandyJSON
协议的非基本字段,或者不能实现HandyJSONEnum
协议的枚举字段,又或者说不希望反序列化影响某个字段,可以在mapping
函数中将它排除。如果不这么做,可能会出现未定义的行为。
class NotHandyJSONType {
var dummy: String?
}
class Cat: HandyJSON {
var id: Int64!
var name: String!
var notHandyJSONTypeProperty: NotHandyJSONType?
var basicTypeButNotWantedProperty: String?
required init() {}
func mapping(mapper: HelpingMapper) {
mapper >>> self.notHandyJSONTypeProperty
mapper >>> self.basicTypeButNotWantedProperty
}
}
let jsonString = "{\"name\":\"cat\",\"id\":\"12345\"}"
if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
print(cat)
}
把Model转换为JSON文本
HandyJSON
还支持对象到字典、到JSON字符串的序列化功能。
基本类型
现在,序列化也要求Model声明服从HandyJSON
协议。
class BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
required init() {}
}
let object = BasicTypes()
object.int = 1
object.doubleOptional = 1.1
object.stringImplicitlyUnwrapped = “hello"
print(object.toJSON()!) // 序列化到字典
print(object.toJSONString()!) // 序列化到JSON字符串
print(object.toJSONString(prettyPrint: true)!) // 序列化为格式化后的JSON字符串
自定义映射和排除
和反序列化一样,只要定义mapping
和exclude
就可以了。被排除的属性,序列化和反序列化都不再影响到它。而在mapping
中定义的Transformer
,同时定义了序列化和反序列的规则,所以只要为属性指明一个Transformer
关系就可以了。
总结
有了HandyJSON
的支持,现在我们可以开心地在Swift中使用JSON了。这个库还在更新中,已经支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建议,快去 https://github.com/alibaba/handyjson 给作者(哈哈没错就是我)提issue吧~~