背景
JSON是移動端開發(fā)常用的應(yīng)用層數(shù)據(jù)交換協(xié)議。最常見的場景便是,客戶端向服務(wù)端發(fā)起網(wǎng)絡(luò)請求,服務(wù)端返回JSON文本,然后客戶端解析這個JSON文本,再把對應(yīng)數(shù)據(jù)展現(xiàn)到頁面上。
但在編程的時候,處理JSON是一件麻煩事。在不引入任何輪子的情況下,我們通常需要先把JSON轉(zhuǎn)為Dictionary,然后還要記住每個數(shù)據(jù)對應(yīng)的Key,用這個Key在Dictionary中取出對應(yīng)的Value來使用。這個過程我們會犯各種錯誤:
- Key拼寫錯了;
- 路徑寫錯了;
- 類型搞錯了;
- 沒拿到值懵逼了;
- 某一天和服務(wù)端約定的某個字段變更了,沒能更新所有用到它的地方;
- ...
為了解決這些問題,很多處理JSON的開源庫應(yīng)運而生。在Swift中,這些開源庫主要朝著兩個方向努力:
- 保持JSON語義,直接解析JSON,但通過封裝使調(diào)用方式更優(yōu)雅、更安全;
- 預(yù)定義Model類,將JSON反序列化為類實例,再使用這些實例;
對于1,使用最廣、評價最好的庫非 SwiftyJSON 莫屬,它很能代表這個方向的核心。它本質(zhì)上仍然是根據(jù)JSON結(jié)構(gòu)去取值,使用起來順手、清晰。但也正因如此,這種做法沒能妥善解決上述的幾個問題,因為Key、路徑、類型仍然需要開發(fā)者去指定;
對于2,我個人覺得這是更合理的方式。由于Model類的存在,JSON的解析和使用都受到了定義的約束,只要客戶端和服務(wù)端約定好了這個Model類,客戶端定義后,在業(yè)務(wù)中使用數(shù)據(jù)時就可以享受到語法檢查、屬性預(yù)覽、屬性補全等好處,而且一旦數(shù)據(jù)定義變更,編譯器會強制所有用到的地方都改過來才能編譯通過,非常安全。這個方向上,開源庫們做的工作,主要就是把JSON文本反序列化到Model類上了。這一類JSON庫有 ObjectMapper、JSONNeverDie、以及我開發(fā)的 HandyJSON 哈哈。
為什么用HandyJSON
在Swift中把JSON反序列化到Model類,在HandyJSON出現(xiàn)以前,主要使用兩種方式:
讓Model類繼承自
NSObject,然后class_copyPropertyList()方法獲取屬性名作為Key,從JSON中取得Value,再通過Objective-C runtime支持的KVC機制為類屬性賦值;如JSONNeverDie;支持純Swift類,但要求開發(fā)者實現(xiàn)
Mapping函數(shù),使用重載的運算符進行賦值,如ObjectMapper;
這兩者都有顯而易見的缺點。前者要求Model繼承自NSObject,非常不優(yōu)雅,且直接否定了用struct來定義Model的方式;后者的Mapping函數(shù)要求開發(fā)者自定義,在其中指明每個屬性對應(yīng)的JSON字段名,代碼侵入大,且仍然容易發(fā)生拼寫錯誤、維護困難等問題。
而HandyJSON另辟蹊徑,采用Swift反射+內(nèi)存賦值的方式來構(gòu)造Model實例,規(guī)避了上述兩個方案遇到的問題,保持原汁原味的Swift類定義。貼一段很能展示這個特點的代碼:
// 假設(shè)這是服務(wù)端返回的統(tǒng)一定義的response格式
class BaseResponse<T: HandyJSON>: HandyJSON {
var code: Int? // 服務(wù)端返回碼
var data: T? // 具體的data的格式和業(yè)務(wù)相關(guān),故用泛型定義
public required init() {}
}
// 假設(shè)這是某一個業(yè)務(wù)具體的數(shù)據(jù)格式定義
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()! // 從對象實例轉(zhuǎn)換到JSON字符串
print(jsonString) // print: {"code":200,"data":{"id":2}}
if let mappedObject = JSONDeserializer<BaseResponse<SampleData>>.deserializeFrom(json: jsonString) { // 從字符串轉(zhuǎn)換為對象實例
print(mappedObject.data?.id)
}
如果是繼承NSObject類的話,Model定義是沒法用泛型的。
把JSON轉(zhuǎn)換為Model
簡單類型
某個Model類想支持通過HandyJSON來反序列化,只需要在定義時,實現(xiàn)HandyJSON協(xié)議,這個協(xié)議只要求實現(xiàn)一個空的init()函數(shù)。
class BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
required init() {}
}
然后假設(shè)我們從服務(wù)端拿到這樣一個JSON文本:
let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}"
引入HandyJSON以后,我們就可以這樣來做反序列化了:
if let object = JSONDeserializer<BasicTypes>.deserializeFrom(json: jsonString) {
// …
}
簡單吧~
支持Struct
如果Model的定義是struct,由于Swift中struct提供了默認(rèn)構(gòu)造函數(shù),所以就不需要再實現(xiàn)空的init()函數(shù)了。但需要注意,如果你為strcut指定了別的構(gòu)造函數(shù),那么就需要保留一個空的實現(xiàn)。
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協(xié)議。不再需要其他特殊處理了。
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)
}
較復(fù)雜的類型,如可選、隱式解包可選、集合等
HandyJSON支持這些非基礎(chǔ)類型,包括嵌套結(jié)構(gòu)。
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)
}
有繼承關(guān)系的Model類
如果某個Model類繼承自另一個Model類,只需要這個父Model類實現(xiàn)HandyJSON協(xié)議就可以:
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支持自定義映射關(guān)系,或者自定義解析過程。你需要實現(xiàn)一個可選的mapping函數(shù),在里邊實現(xiàn)NSString值(HandyJSON會把對應(yīng)的JSON字段轉(zhuǎn)換為NSString)轉(zhuǎn)換為你需要的字段類型。
所以無法直接支持的類型,都可以用這個方式支持。
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中存在因為某些原因不能實現(xiàn)HandyJSON協(xié)議的非基本字段,或者不能實現(xiàn)HandyJSONEnum協(xié)議的枚舉字段,又或者說不希望反序列化影響某個字段,可以在mapping函數(shù)中將它排除。如果不這么做,可能會出現(xiàn)未定義的行為。
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轉(zhuǎn)換為JSON文本
HandyJSON還支持對象到字典、到JSON字符串的序列化功能。
基本類型
現(xiàn)在,序列化也要求Model聲明服從HandyJSON協(xié)議。
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,同時定義了序列化和反序列的規(guī)則,所以只要為屬性指明一個Transformer關(guān)系就可以了。
總結(jié)
有了HandyJSON的支持,現(xiàn)在我們可以開心地在Swift中使用JSON了。這個庫還在更新中,已經(jīng)支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建議,快去 https://github.com/alibaba/handyjson 給作者(哈哈沒錯就是我)提issue吧~~