[HandyJSON] 在Swift語言中處理JSON - 轉(zhuǎn)換JSON和Model

背景

JSON是移動端開發(fā)常用的應(yīng)用層數(shù)據(jù)交換協(xié)議。最常見的場景便是,客戶端向服務(wù)端發(fā)起網(wǎng)絡(luò)請求,服務(wù)端返回JSON文本,然后客戶端解析這個JSON文本,再把對應(yīng)數(shù)據(jù)展現(xiàn)到頁面上。

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

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

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

  1. 保持JSON語義,直接解析JSON,但通過封裝使調(diào)用方式更優(yōu)雅、更安全;
  2. 預(yù)定義Model類,將JSON反序列化為類實例,再使用這些實例;

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

對于2,我個人覺得這是更合理的方式。由于Model類的存在,JSON的解析和使用都受到了定義的約束,只要客戶端和服務(wù)端約定好了這個Model類,客戶端定義后,在業(yè)務(wù)中使用數(shù)據(jù)時就可以享受到語法檢查、屬性預(yù)覽、屬性補全等好處,而且一旦數(shù)據(jù)定義變更,編譯器會強制所有用到的地方都改過來才能編譯通過,非常安全。這個方向上,開源庫們做的工作,主要就是把JSON文本反序列化到Model類上了。這一類JSON庫有 ObjectMapperJSONNeverDie、以及我開發(fā)的 HandyJSON 哈哈。

為什么用HandyJSON

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

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

  2. 支持純Swift類,但要求開發(fā)者實現(xiàn)Mapping函數(shù),使用重載的運算符進行賦值,如ObjectMapper;

這兩者都有顯而易見的缺點。前者要求Model繼承自NSObject,非常不優(yōu)雅,且直接否定了用struct來定義Model的方式;后者的Mapping函數(shù)要求開發(fā)者自定義,在其中指明每個屬性對應(yīng)的JSON字段名,代碼侵入大,且仍然容易發(fā)生拼寫錯誤、維護困難等問題。

HandyJSON另辟蹊徑,采用Swift反射+內(nèi)存賦值的方式來構(gòu)造Model實例,規(guī)避了上述兩個方案遇到的問題,保持原汁原味的Swift類定義。貼一段很能展示這個特點的代碼:

// 假設(shè)這是服務(wù)端返回的統(tǒng)一定義的response格式
class BaseResponse<T: HandyJSON>: HandyJSON {
    var code: Int? // 服務(wù)端返回碼
    var data: T? // 具體的data的格式和業(yè)務(wù)相關(guān),故用泛型定義

    public required init() {}
}

// 假設(shè)這是某一個業(yè)務(wù)具體的數(shù)據(jù)格式定義
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()! // 從對象實例轉(zhuǎn)換到JSON字符串
print(jsonString) // print: {"code":200,"data":{"id":2}}

if let mappedObject = JSONDeserializer<BaseResponse<SampleData>>.deserializeFrom(json: jsonString) { // 從字符串轉(zhuǎn)換為對象實例
    print(mappedObject.data?.id)
}

如果是繼承NSObject類的話,Model定義是沒法用泛型的。

把JSON轉(zhuǎn)換為Model

簡單類型

某個Model類想支持通過HandyJSON來反序列化,只需要在定義時,實現(xiàn)HandyJSON協(xié)議,這個協(xié)議只要求實現(xiàn)一個空的init()函數(shù)。

class BasicTypes: HandyJSON {
    var int: Int = 2
    var doubleOptional: Double?
    var stringImplicitlyUnwrapped: String!

    required init() {}
}

然后假設(shè)我們從服務(wù)端拿到這樣一個JSON文本:

let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}"

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

if let object = JSONDeserializer<BasicTypes>.deserializeFrom(json: jsonString) {
    // …
}

簡單吧~

支持Struct

如果Model的定義是struct,由于Swift中struct提供了默認(rèn)構(gòu)造函數(shù),所以就不需要再實現(xiàn)空的init()函數(shù)了。但需要注意,如果你為strcut指定了別的構(gòu)造函數(shù),那么就需要保留一個空的實現(xiàn)。

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協(xié)議。不再需要其他特殊處理了。

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)
}

較復(fù)雜的類型,如可選、隱式解包可選、集合等

HandyJSON支持這些非基礎(chǔ)類型,包括嵌套結(jié)構(gòu)。

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)
}

有繼承關(guān)系的Model類

如果某個Model類繼承自另一個Model類,只需要這個父Model類實現(xiàn)HandyJSON協(xié)議就可以:

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支持自定義映射關(guān)系,或者自定義解析過程。你需要實現(xiàn)一個可選的mapping函數(shù),在里邊實現(xiàn)NSString值(HandyJSON會把對應(yīng)的JSON字段轉(zhuǎn)換為NSString)轉(zhuǎn)換為你需要的字段類型。

所以無法直接支持的類型,都可以用這個方式支持。

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中存在因為某些原因不能實現(xiàn)HandyJSON協(xié)議的非基本字段,或者不能實現(xiàn)HandyJSONEnum協(xié)議的枚舉字段,又或者說不希望反序列化影響某個字段,可以在mapping函數(shù)中將它排除。如果不這么做,可能會出現(xiàn)未定義的行為。

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轉(zhuǎn)換為JSON文本

HandyJSON還支持對象到字典、到JSON字符串的序列化功能。

基本類型

現(xiàn)在,序列化也要求Model聲明服從HandyJSON協(xié)議。

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字符串

自定義映射和排除

和反序列化一樣,只要定義mappingexclude就可以了。被排除的屬性,序列化和反序列化都不再影響到它。而在mapping中定義的Transformer,同時定義了序列化和反序列的規(guī)則,所以只要為屬性指明一個Transformer關(guān)系就可以了。

總結(jié)

有了HandyJSON的支持,現(xiàn)在我們可以開心地在Swift中使用JSON了。這個庫還在更新中,已經(jīng)支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建議,快去 https://github.com/alibaba/handyjson 給作者(哈哈沒錯就是我)提issue吧~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評論 19 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,026評論 4 61
  • 轉(zhuǎn)載自:https://github.com/Tim9Liu9/TimLiu-iOS[https://github...
    香橙柚子閱讀 9,140評論 0 36
  • 文/蘭舟醬 疼痛讓女人成為女人。記得聽某生物老師說過,內(nèi)臟的痛法與肌膚的痛法是怎樣不同為何不同。內(nèi)臟的鈍痛和肌...
    蘭舟醬閱讀 399評論 0 2
  • 快意恩仇豪情灑,柔情江湖意綿長 還記得你懵懂無知時,內(nèi)心那個美好的江湖夢嗎?哪個你自己創(chuàng)造的美好世界,一切皆由自己...
    菩提子的葉閱讀 574評論 9 5

友情鏈接更多精彩內(nèi)容