HandyJSON阿里巴巴開源的Swift環(huán)境下用的Json轉(zhuǎn)模型工具乓土。
為什么用HandyJSON
在Swift中把JSON反序列化到Model類,在HandyJSON出現(xiàn)以前妆丘,主要使用兩種方式:
1. 讓Model類繼承自NSObject扣汪,然后class_copyPropertyList()方法獲取屬性名作為Key,從JSON中取得Value,再通過Objective-C runtime支持的KVC機(jī)制為類屬性賦值怔软;如JSONNeverDie
2. 支持純Swift類,但要求開發(fā)者實(shí)現(xiàn)Mapping函數(shù)择镇,使用重載的運(yùn)算符進(jìn)行賦值挡逼,如ObjectMapper;
這兩者都有顯而易見的缺點(diǎn)腻豌。前者要求Model繼承自NSObject家坎,非常不優(yōu)雅,且直接否定了用struct來定義Model的方式饲梭;后者的Mapping函數(shù)要求開發(fā)者自定義乘盖,在其中指明每個(gè)屬性對(duì)應(yīng)的JSON字段名焰檩,代碼侵入大憔涉,且仍然容易發(fā)生拼寫錯(cuò)誤、維護(hù)困難等問題析苫。
而HandyJSON獨(dú)辟蹊徑兜叨,采用Swift反射+內(nèi)存賦值的方式來構(gòu)造Model實(shí)例,規(guī)避了上述兩個(gè)方案遇到的問題衩侥。
JSON轉(zhuǎn)為Model
簡(jiǎn)單類型
某個(gè)Model類想支持通過HandyJSON來反序列化国旷,只需要在定義時(shí),實(shí)現(xiàn)HandyJSON協(xié)議茫死,這個(gè)協(xié)議只要求實(shí)現(xiàn)一個(gè)空的init()函數(shù)跪但。
比如我們和服務(wù)端約定了一個(gè)Animal數(shù)據(jù),里面有name/id/num字段峦萎,那么我們這樣定義Animal類:
class Animal: HandyJSON {
var name: String?
var id: String?
var num: Int?
required init() {} // 如果定義是struct屡久,連init()函數(shù)都不用聲明;
}
然后假設(shè)我們從服務(wù)端拿到這樣一個(gè)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)
}
比較復(fù)雜的類型
HandyJSON支持在類定義里使用各種形式的基本屬性被环,包括可選(?),隱式解包可選(!)详幽,數(shù)組(Array)筛欢,字典(Dictionary),Objective-C基本類型(NSString唇聘、NSNumber)版姑,各種類型的嵌套([Int]?、[String]?迟郎、[Int]!漠酿、...)等等。比如下面這個(gè)看起來比較復(fù)雜的類型:
struct Cat: HandyJSON {
var id: Int64!
var name: String!
var friend: [String]?
var weight: Double?
var alive: Bool = true
var color: NSString?
}
一樣輕松轉(zhuǎn)換:
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類中的某個(gè)屬性是另一個(gè)自定義的Model類谎亩,那么只要那個(gè)Model類也實(shí)現(xiàn)了HandyJSON協(xié)議炒嘲,就一樣可以轉(zhuǎn)換:
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)
}
有繼承關(guān)系的類型
如果某個(gè)Model類繼承自另一個(gè)Model類宇姚,只需要這個(gè)父Model類實(shí)現(xiàn)HandyJSON協(xié)議就可以
自定義解析方式
HandyJSON還提供了一個(gè)擴(kuò)展能力,就是允許自行定義Model類某個(gè)字段的解析Key夫凸、解析方式浑劳。我們經(jīng)常會(huì)有這樣的需求:
1. 某個(gè)Model中,我們不想使用和服務(wù)端約定的key作為屬性名夭拌,想自己定一個(gè)魔熏;
2. 有些類型如enum、tuple是無(wú)法直接從JSON中解析出來的鸽扁,但我們?cè)贛odel類中有這樣的屬性蒜绽;
HandyJSON協(xié)議提供了一個(gè)可選的mapping()函數(shù),我們可以在其中指定某個(gè)字段用什么Key桶现、或者用什么方法從JSON中解析出它的值躲雅。如我們有一個(gè)Model類和一個(gè)服務(wù)端返回的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是對(duì)應(yīng)不上的骡和;而對(duì)于parent這個(gè)屬性來說相赁,它是一個(gè)元組,做不到從JSON中的"Tom/Lily"解析出來慰于。所以我們要定義一個(gè)Mapping函數(shù)來做這兩個(gè)支持:
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 字段用這個(gè)方法去解析
mapper.specify(property: &parent) { (rawString) -> (String, String) in
let parentNames = rawString.characters.split{$0 == "/"}.map(String.init)
return (parentNames[0], parentNames[1])
}
}
}
指定反序列化JSON中某個(gè)節(jié)點(diǎn)
有時(shí)候服務(wù)端返回給我們的JSON文本包含了大量的狀態(tài)信息钮科,和Model無(wú)關(guān),比如statusCode婆赠,debugMessage等绵脯,或者有用的數(shù)據(jù)是在某個(gè)節(jié)點(diǎn)以下,那么我們可以指定反序列化哪個(gè)節(jié)點(diǎn):
struct Cat: HandyJSON {
var id: Int64!
var name: String!
}
// 服務(wù)端返回了這個(gè)JSON休里,我們想解析的只有data里的cat
let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}"
// 那么蛆挫,我們指定解析 "data.cat",通過點(diǎn)來表達(dá)路徑
if let cat = JSONDeserializer.deserializeFrom(json: jsonString, designatedPath: "data.cat") {
print(cat.name)
}
實(shí)戰(zhàn)
先來看看HandyJSON的幾個(gè)JSON轉(zhuǎn)模型的序列化方法:
JSONDeserializer<XXXModel>.deserializeFrom(dict: NSDictionary?)
JSONDeserializer<XXXModel>.deserializeFrom(json: String?)
JSONDeserializer<XXXModel>.deserializeModelArrayFrom(json: String?)
JSONDeserializer<XXXModel>.deserializeFrom(dict: NSDictionary?, designatedPath: String?)
JSONDeserializer<XXXModel>.deserializeFrom(json: String?, designatedPath: String?)
JSONDeserializer<XXXModel>.deserializeModelArrayFrom(json: String?, designatedPath: String?)
再來看看我們的模擬數(shù)據(jù)份帐,并創(chuàng)建模型
let baseInfo: NSDictionary = ["build_name":"置信·原墅",
"build_address":"學(xué)院中路與金橋路交匯處東北側(cè)",
"area_address":"浙江省溫州市鹿城區(qū)五馬街道",
"build_num": "12",
"room_num": 588,
"detail_address":["province":"浙江省",
"city":"溫州市",
"district":"鹿城區(qū)",
"street":"五馬街道"]]
import HandyJSON
struct HouseInfo: HandyJSON {
var build_name: String?
var build_address: String?
var build_num: Int?
var room_num: Int?
var area_address: String?
var detail_address: LvFourAddressModel?
}
struct LvFourAddressModel: HandyJSON {
var province: String?
var city: String?
var district: String?
var street: String?
}
我們選擇相應(yīng)的方法進(jìn)行序列號(hào)得到:
let baseInfo = JSONDeserializer<HouseInfo>.deserializeFrom(dict: baseInfo)
print(baseInfo?.build_num)
// Optional(12)璃吧, 服務(wù)器返回的是字符串,但是我們定義的是Int類型废境,得到了Int畜挨, nice
print(baseInfo?.area_address)
// Optional("浙江省溫州市鹿城區(qū)五馬街道"), 如果將area_address 定義為Int噩凹,那么得到nil
// 如果修改 模型中 build_name 為 name 巴元, 那么打印 name 將會(huì)因?yàn)闆]有得到對(duì)應(yīng)字段的值打印 nil,
把Model轉(zhuǎn)換為JSON文本
基本類型
如果只需要進(jìn)行序列化驮宴,那么在定義Model類時(shí)逮刨,不需要做任何特殊的改動(dòng)。任何一個(gè)類的實(shí)例,直接調(diào)用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參數(shù)來指定獲得的是否是格式化后的JSON串
復(fù)雜類型
即使Model類中有別的Model類啥的,都一樣支持
總結(jié)
swift語(yǔ)言崇尚簡(jiǎn)便快速的寫法睬愤,那么相比較之前討論的SwiftyJSON片仿,objectMapper,對(duì)于我這種初學(xué)者尤辱,更傾向于先用HandyJSON砂豌,作為項(xiàng)目中的JSON-Model互轉(zhuǎn)的工具,且對(duì)于小項(xiàng)目絕對(duì)夠了光督,等往后遇到什么坑再解決吧阳距。等項(xiàng)目越來越大,接觸到各種問題時(shí)结借,再回過頭來看看如何解決筐摘,哪種工具更適合。