阿里巴巴最新開源項目 - [HandyJSON] 在Swift中優雅地處理JSON

 

項目名稱:HandyJSONgit

項目地址:https://github.com/alibaba/handyjsongithub

背景

JSON是移動端開發經常使用的應用層數據交換協議。最多見的場景即是,客戶端向服務端發起網絡請求,服務端返回JSON文本,而後客戶端解析這個JSON文本,再把對應數據展示到頁面上。編程

但在編程的時候,處理JSON是一件麻煩事。在不引入任何輪子的狀況下,咱們一般須要先把JSON轉爲Dictionary,而後還要記住每一個數據對應的Key,用這個Key在Dictionary中取出對應的Value來使用。這個過程咱們會犯各類錯誤:json

  • Key拼寫錯了;
  • 路徑寫錯了;
  • 類型搞錯了;
  • 沒拿到值懵逼了;
  • 某一天和服務端約定的某個字段變動了,沒能更新全部用到它的地方;
  • ...

爲了解決這些問題,不少處理JSON的開源庫應運而生。在Swift中,這些開源庫主要朝着兩個方向努力:數組

  1. 保持JSON語義,直接解析JSON,但經過封裝使調用方式更優雅、更安全;
  2. 預約義Model類,將JSON反序列化爲類實例,再使用這些實例;

對於1,使用最廣、評價最好的庫非 SwiftyJSON 莫屬,它很能表明這個方向的核心。它本質上仍然是根據JSON結構去取值,使用起來順手、清晰。但也正因如此,這種作法沒能妥善解決上述的幾個問題,由於Key、路徑、類型仍然須要開發者去指定;安全

對於2,我我的以爲這是更合理的方式。因爲Model類的存在,JSON的解析和使用都受到了定義的約束,只要客戶端和服務端約定好了這個Model類,客戶端定義後,在業務中使用數據時就能夠享受到語法檢查、屬性預覽、屬性補全等好處,並且一旦數據定義變動,編譯器會強制全部用到的地方都改過來才能編譯經過,很是安全。這個方向上,開源庫們作的工做,主要就是把JSON文本反序列化到Model類上了。這一類JSON庫有 ObjectMapperJSONNeverDieHandyJSON 等。而 HandyJSON 是其中使用最舒服的一個庫,本文將介紹用 HandyJSON 來進行Model和JSON間的互相轉換。網絡

爲何用HandyJSON

在Swift中把JSON反序列化到Model類,在HandyJSON出現之前,主要使用兩種方式:app

  1. 讓Model類繼承自NSObject,而後class_copyPropertyList()方法獲取屬性名做爲Key,從JSON中取得Value,再經過Objective-C runtime支持的KVC機制爲類屬性賦值;如JSONNeverDie函數

  2. 支持純Swift類,但要求開發者實現Mapping函數,使用重載的運算符進行賦值,如ObjectMapperui

這二者都有顯而易見的缺點。前者要求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無關,好比statusCodedebugMessage等,或者有用的數據是在某個節點如下,那麼咱們能夠指定反序列化哪一個節點:

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做爲屬性名,想本身定一個;
  • 有些類型如enumtuple是沒法直接從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吧~~

相關文章
相關標籤/搜索