在Swift語言中處理JSON - 轉換JSON和Model

tsmd6369 8年前發布 | 25K 次閱讀 JSON Swift Apple Swift開發

背景

JSON是移動端開發常用的應用層數據交換協議。最常見的場景便是,客戶端向服務端發起網絡請求,服務端返回JSON文本,然后客戶端解析這個JSON文本,再把對應數據展現到頁面上。

但在編程的時候,處理JSON是一件麻煩事。在不引入任何輪子的情況下,我們通常需要先把JSON轉為Dictionary,然后還要記住每個數據對應的Key,用這個Key在Dictionary中取出對應的Value來使用。這個過程我們會犯各種錯誤:

  • Key拼寫錯了;

  • 路徑寫錯了;

  • 類型搞錯了;

  • 沒拿到值懵逼了;

  • 某一天和服務端約定的某個字段變更了,沒能更新所有用到它的地方;

  • ...

為了解決這些問題,很多處理JSON的開源庫應運而生。在Swift中,這些開源庫主要朝著兩個方向努力:

  1. 保持JSON語義,直接解析JSON,但通過封裝使調用方式更優雅、更安全;

  2. 預定義Model類,將JSON反序列化為類實例,再使用這些實例;

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

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

為什么用HandyJSON

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

  1. 讓Model類繼承自 NSObject ,然后 class_copyPropertyList() 方法獲取屬性名作為Key,從JSON中取得Value,再通過 Objective-C runtime 支持的 KVC 機制為類屬性賦值;如 JSONNeverDie ;

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

這兩者都有顯而易見的缺點。前者要求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()函數都不用聲明;

}</code></pre>

然后假設我們從服務端拿到這樣一個JSON文本:

let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}"

引入 HandyJSON 以后,我們就可以這樣來做反序列化了:

if let animal = JSONDeserializer

.deserializeFrom(json: jsonString) { print(animal.name) print(animal.id) print(animal.num) }

</code></pre>

簡單吧~

比較復雜的類型

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

.deserializeFrom(json: jsonString) { print(cat.xxx) }

</code></pre>

嵌套的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

.deserializeFrom(json: jsonString) { print(composition) }

</code></pre>

指定反序列化JSON中某個節點

有時候服務端返回給我們的JSON文本包含了大量的狀態信息,和Model無關,比如 statusCode , debugMessage 等,或者有用的數據是在某個節點以下,那么我們可以指定反序列化哪個節點:

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

.deserializeFrom(json: jsonString, designatedPath: "data.cat") { print(cat.name) }

</code></pre>

有繼承關系的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

.deserializeFrom(json: jsonString) { print(cat) }

</code></pre>

自定義解析方式

HandyJSON 還提供了一個擴展能力,就是允許自行定義Model類某個字段的解析Key、解析方式。我們經常會有這樣的需求:

  • 某個Model中,我們不想使用和服務端約定的key作為屬性名,想自己定一個;

  • 有些類型如 enum 、 tuple 是無法直接從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\"}"</code></pre>

可以看到, 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])
    }
}

}</code></pre>

就這樣, 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)!)</code></pre>

可以通過 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)!)</code></pre>

總結

有了 HandyJSON 的支持,現在我們可以開心地在Swift中使用JSON了。這個庫還在更新中,已經支持了Swift 2.2+, Swift 3.0+。

 

來自:http://www.cocoachina.com/swift/20161010/17711.html

 

 本文由用戶 tsmd6369 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!