Json轉(zhuǎn)模型3--HandyJSON

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í)结借,再回過頭來看看如何解決筐摘,哪種工具更適合。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末映跟,一起剝皮案震驚了整個(gè)濱河市蓄拣,隨后出現(xiàn)的幾起案子扬虚,更是在濱河造成了極大的恐慌努隙,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辜昵,死亡現(xiàn)場(chǎng)離奇詭異荸镊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)堪置,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門躬存,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舀锨,你說我怎么就攤上這事岭洲。” “怎么了坎匿?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵盾剩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我替蔬,道長(zhǎng)告私,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任承桥,我火速辦了婚禮驻粟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凶异。我一直安慰自己蜀撑,他們只是感情好挤巡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酷麦,像睡著了一般玄柏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贴铜,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天粪摘,我揣著相機(jī)與錄音,去河邊找鬼绍坝。 笑死徘意,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的轩褐。 我是一名探鬼主播椎咧,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼把介!你這毒婦竟也來了勤讽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拗踢,失蹤者是張志新(化名)和其女友劉穎脚牍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巢墅,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诸狭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了君纫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驯遇。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蓄髓,靈堂內(nèi)的尸體忽然破棺而出叉庐,到底是詐尸還是另有隱情,我是刑警寧澤会喝,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布陡叠,位于F島的核電站,受9級(jí)特大地震影響好乐,放射性物質(zhì)發(fā)生泄漏匾竿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一蔚万、第九天 我趴在偏房一處隱蔽的房頂上張望岭妖。 院中可真熱鬧,春花似錦、人聲如沸昵慌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)斋攀。三九已至已卷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淳蔼,已是汗流浹背侧蘸。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹉梨,地道東北人讳癌。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像存皂,于是被迫代替她去往敵國(guó)和親晌坤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)旦袋、插件骤菠、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,098評(píng)論 4 62
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)疤孕,斷路器商乎,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • JSON是移動(dòng)端開發(fā)常用的應(yīng)用層數(shù)據(jù)交換協(xié)議。最常見的場(chǎng)景便是胰柑,客戶端向服務(wù)端發(fā)起網(wǎng)絡(luò)請(qǐng)求截亦,服務(wù)端返回JSON文本...
    水落斜陽(yáng)閱讀 3,531評(píng)論 0 15
  • http://blog.sina.com.cn/s/articlelist_1324829101_0_1.html
    neobuger閱讀 87評(píng)論 0 0
  • 文:花生 圖:網(wǎng)絡(luò) 大街上爬泥,有個(gè)小男孩吵著要買棉花糖柬讨。媽媽指著他手里的小糖人,說:“剛買的孫悟空還沒吃呢袍啡,怎么...
    作者花生閱讀 1,017評(píng)論 1 7