[HandyJSON] 在Swift語言中處理JSON - 轉換JSON和Model
背景
JSON是移動端開發常用的應用層資料交換協議。最常見的場景便是,客戶端向服務端發起網路請求,服務端返回JSON文字,然後客戶端解析這個JSON文字,再把對應資料展現到頁面上。
但在程式設計的時候,處理JSON是一件麻煩事。在不引入任何輪子的情況下,我們通常需要先把JSON轉為Dictionary,然後還要記住每個資料對應的Key,用這個Key在Dictionary中取出對應的Value來使用。這個過程我們會犯各種錯誤:
- Key拼寫錯了;
- 路徑寫錯了;
- 型別搞錯了;
- 沒拿到值懵逼了;
- 某一天和服務端約定的某個欄位變更了,沒能更新所有用到它的地方;
- ...
為了解決這些問題,很多處理JSON的開源庫應運而生。在Swift中,這些開源庫主要朝著兩個方向努力:
- 保持JSON語義,直接解析JSON,但通過封裝使呼叫方式更優雅、更安全;
- 預定義Model類,將JSON反序列化為類例項,再使用這些例項;
對於1,使用最廣、評價最好的庫非 SwiftyJSON 莫屬,它很能代表這個方向的核心。它本質上仍然是根據JSON結構去取值,使用起來順手、清晰。但也正因如此,這種做法沒能妥善解決上述的幾個問題,因為Key、路徑、型別仍然需要開發者去指定;
對於2,我個人覺得這是更合理的方式。由於Model類的存在,JSON的解析和使用都受到了定義的約束,只要客戶端和服務端約定好了這個Model類,客戶端定義後,在業務中使用資料時就可以享受到語法檢查、屬性預覽、屬性補全等好處,而且一旦資料定義變更,編譯器會強制所有用到的地方都改過來才能編譯通過,非常安全。這個方向上,開源庫們做的工作,主要就是把JSON文字反序列化到Model類上了。這一類JSON庫有 ObjectMapper、JSONNeverDie、以及我開發的 HandyJSON 哈哈。
為什麼用HandyJSON
在Swift中把JSON反序列化到Model類,在HandyJSON
出現以前,主要使用兩種方式:
讓Model類繼承自
NSObject
,然後class_copyPropertyList()
方法獲取屬性名作為Key,從JSON中取得Value,再通過Objective-C runtime
支援的KVC
機制為類屬性賦值;如JSONNeverDie
;支援純Swift類,但要求開發者實現
Mapping
函式,使用過載的運算子進行賦值,如ObjectMapper
;
這兩者都有顯而易見的缺點。前者要求Model繼承自NSObject
,非常不優雅,且直接否定了用struct
來定義Model的方式;後者的Mapping
函式要求開發者自定義,在其中指明每個屬性對應的JSON欄位名,程式碼侵入大,且仍然容易發生拼寫錯誤、維護困難等問題。
而HandyJSON
另闢蹊徑,採用Swift反射
+記憶體賦值
的方式來構造Model例項,規避了上述兩個方案遇到的問題,保持原汁原味的Swift類定義。貼一段很能展示這個特點的程式碼:
// 假設這是服務端返回的統一定義的response格式
class BaseResponse<T: HandyJSON>: HandyJSON {
var code: Int? // 服務端返回碼
var data: T? // 具體的data的格式和業務相關,故用泛型定義
public required init() {}
}
// 假設這是某一個業務具體的資料格式定義
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()! // 從物件例項轉換到JSON字串
print(jsonString) // print: {"code":200,"data":{"id":2}}
if let mappedObject = JSONDeserializer<BaseResponse<SampleData>>.deserializeFrom(json: jsonString) { // 從字串轉換為物件例項
print(mappedObject.data?.id)
}
如果是繼承NSObject
類的話,Model定義是沒法用泛型的。
把JSON轉換為Model
簡單型別
某個Model類想支援通過HandyJSON
來反序列化,只需要在定義時,實現HandyJSON
協議,這個協議只要求實現一個空的init()
函式。
class BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
required init() {}
}
然後假設我們從服務端拿到這樣一個JSON文字:
let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}"
引入HandyJSON
以後,我們就可以這樣來做反序列化了:
if let object = JSONDeserializer<BasicTypes>.deserializeFrom(json: jsonString) {
// …
}
簡單吧~
支援Struct
如果Model的定義是struct,由於Swift中struct提供了預設建構函式,所以就不需要再實現空的init()
函式了。但需要注意,如果你為strcut指定了別的建構函式,那麼就需要保留一個空的實現。
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
協議。不再需要其他特殊處理了。
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)
}
較複雜的型別,如可選、隱式解包可選、集合等
HandyJSON支援這些非基礎型別,包括巢狀結構。
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)
}
有繼承關係的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支援自定義對映關係,或者自定義解析過程。你需要實現一個可選的mapping
函式,在裡邊實現NSString
值(HandyJSON會把對應的JSON欄位轉換為NSString)轉換為你需要的欄位型別。
所以無法直接支援的型別,都可以用這個方式支援。
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中存在因為某些原因不能實現HandyJSON
協議的非基本欄位,或者不能實現HandyJSONEnum
協議的列舉欄位,又或者說不希望反序列化影響某個欄位,可以在mapping
函式中將它排除。如果不這麼做,可能會出現未定義的行為。
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轉換為JSON文字
HandyJSON
還支援物件到字典、到JSON字串的序列化功能。
基本型別
現在,序列化也要求Model宣告服從HandyJSON
協議。
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
,同時定義了序列化和反序列的規則,所以只要為屬性指明一個Transformer
關係就可以了。
總結
有了HandyJSON
的支援,現在我們可以開心地在Swift中使用JSON了。這個庫還在更新中,已經支援了Swift 2.2+, Swift 3.0+。如果大家有什麼需求或者建議,快去 https://github.com/alibaba/handyjson 給作者(哈哈沒錯就是我)提issue吧~~
相關文章
- 在PHP語言中使用JSONPHPJSON
- Swift Json 解析異常處理SwiftJSON
- 在PHP語言中使用JSON和將json還原成陣列PHPJSON陣列
- iOS 手動打造JSON Model轉換庫iOSJSON
- 在Go語言中,怎樣使用Json的方法?GoJSON
- json字串和json格式物件的轉換JSON字串物件
- 在REST SOE中處理JSONRESTJSON
- Go語言轉換JSON資料GoJSON
- flutter json_annotation和json_serializable處理json資料序列化FlutterJSON
- Json轉換(一)JSON
- Json轉換(二)JSON
- Json轉換(三)JSON
- Java語言中字元的處理 (轉)Java字元
- Flutter如何高效的JSON轉ModelFlutterJSON
- Python處理JSONPythonJSON
- 處理JSON資料JSON
- Android中實現JSON字串和JSON物件的轉換AndroidJSON字串物件
- 使用Jackson在Java中處理JSONJavaJSON
- 在.NET使用Newtonsoft.Json轉換,讀取,寫入jsonJSON
- Go語言中JSON標籤的用法與技巧GoJSON
- 在 AngularJS 中將 XML 轉換為 JSONAngularXMLJSON
- Flutter如何更便捷的json轉modelFlutterJSON
- Map 轉json資料,json資料轉換為MapJSON
- PostgreSQL處理JSON入門SQLJSON
- flutter json資料處理FlutterJSON
- flutter demo (三):json處理FlutterJSON
- golang json處理問題GolangJSON
- Hive處理Json資料HiveJSON
- 「譯」使用 System.Net.Http.Json 高效處理JsonHTTPJSON
- JSON.NET框架實現C#物件和JSON字串的轉換JSON框架C#物件字串
- eval() JSON轉換為物件JSON物件
- 自寫Json轉換工具JSON
- 資料集轉換JSONJSON
- JavaScript:如何將JSON物件轉換成JSON字串呢JavaScriptJSON物件字串
- 資料匯入與預處理實驗二---json格式檔案轉換JSON
- Swift iOS : 解析jsonSwiftiOSJSON
- Swift Json解析探索SwiftJSON
- json字串和js物件之間相互轉換JSON字串物件