純Swift專案-JSON(Basic.frameworks)

小歪發表於2019-01-18

Swift JSON 發展史

  1. 最開始的時候還是使用NSJSONSerialization轉成字典和陣列來使用!
  2. 後來蘋果用Swift重新實現了JSONSerialization可以避免用NSArrayNSDictionary來橋接,提高解析效率。
  3. 隨後很多三方JSON庫相繼出現,例如:SwiftyJSONHandyJSON......等,請原諒我一直沒有用過這些三方庫,雖然有參考學習過,但我一直維護改進自己封裝的JSON庫,這期間雖然使用JSON的簡便性有所提高,但我仍然覺得很麻煩。
  4. Swift 4.0 開始,蘋果提供了Codable協議和JSONDecoderJSONEncoder,這一改進使得一眾三方庫黯然失色,將JSON到模型的便捷方式提升到了一個新的高度,但這種方式還是過於強硬,模型型別過於死板,稍有不慎就會因為型別不符導致解析失敗,因此我將自己的JSON庫新增了Codable協議支援,作為官方庫的一個有益補充。
  5. Swift 4.2 的時候dynamicMemberLookup(動態屬性)的加入又給(軟)JSON模型加了一劑強心針。

例子JSON

{"name":"小王","age":5,"reads":["格林童話","安徒生童話"]}

以前的三方JSON庫的缺陷

以前的三方JSON庫在將相對動態的json資料解析到模型的時候除了要寫屬性型別外,在構造方法中,也要寫上對應的key,模型寫起來很囉嗦,例如:

struct User {
    var name:String
    var age:Int
    var reads:[String]
    
    init(_ json:JSON) {
        name = json["name"].string
        age = json["age"].int
        reads = json["reads"].array
    }
}
複製程式碼

屬性越多,構造方法中寫的也越多,相同的鍵字串和變數名顯得十分囉嗦


JSONDecoder 的缺陷

自從蘋果革命性的增加了Codable協議,現在只需要寫模型的屬性,而不必寫構造方法

struct User: Codable {
    var name:String
    var age:Int
    var reads:[String]
}
複製程式碼

解析的時候也十分簡便

let user = try! JSONDecoder().decode(User.self, from: data)
複製程式碼

可能是因為蘋果作為大公司,有著極度嚴苛的編碼規範和前後端協作標準,因此JSONDecoder對型別的要求是十分嚴苛的,如果換成下面這段JSON內容就會解析失敗:

{"name":"小王","age":"5","reads":["格林童話","安徒生童話"]}

這其中將age的型別變成了字串的"5"與模型的Int型別不符,使得整個JSON都無法解析。實際的開發中前後端未必有那麼嚴苛的規範,也不一定總能碰到靠譜的後端,因此經常因為各種型別不符原因解析模型失敗就很蛋疼。


可能您會說這只是小問題,只要規範開發就好了,那下面這個問題就是硬傷。


比如說一般情況下後端給我們返回的JSON都有幾個固定欄位判斷返回結果可用性,例如:

{
    "errorCode":0,
    "errorMessage":"成功",
    "result":{}
}
複製程式碼
{
    "errorCode":1,
    "errorMessage":"缺少引數",
    "result":null
}
複製程式碼

在上面的例子中errorCode是伺服器返回的錯誤狀態0表示請求成功errorMessage是錯誤狀態提示文字,result根據不同的API介面,返回不同格式的JSON內容。 如果想寫一個模型的話:

struct ResponseJSON: Codable {
    var errorMessage: String
    var errorCode: Int
    var result: ???????
}
複製程式碼

這就尷尬了,result屬性的型別沒法填,因為無法確定此刻它該使用什麼模型。 解決方法如下:

  1. 不使用模型,改用字典,但這樣就回歸原始,用起來不方便。
  2. result屬性型別使用[String:Any],雖然當下使用了模型,但後面使用很不方便。
  3. ResponseJSON使用泛型定義由呼叫網路請求介面預傳入result所需的模型
  4. 定義一種動態型別儲存未完全解析的JSON,容許對應介面使用result欄位時懶解析到恰當的模型

其中,比較好的方法是34, 但方法3還是有缺陷,相同的API返回的JSON結構就一定相同麼?可能有一個type屬性表示著另一個屬性是什麼結構。這種時候,預傳入泛型就無能為力了。

因此我們需要定義一個符合Codable協議的動態型別來儲存未完全解析的JSON, 在需要的時候可以懶解析成所需模型。

public enum JSON: Codable {
    case object (Object)
    case array  (Array)
    case string (String)
    case number (Number)
    case bool   (Bool)
    case null
    case error  (Error, ignore:[String])
}
複製程式碼

程式碼當然不止這點,詳細內容可以參考(Basic.frameworks)中關於JSON的部分

  • 現在,我們可以將ResponseJSON定義成:
struct ResponseJSON: Codable {
    var errorMessage: String
    var errorCode: Int
    var result: JSON
}
複製程式碼

在需要的時候使用

let user = try! JSON.Decoder().decode(User.self, from: responseJSON.result)
複製程式碼

或者其他模型


  • 前例(JSON)中objectarraystringnumberboolnull這6個是我們熟知的json資料型別,而error是什麼呢?為什麼要新增它?

顧名思義,error就是錯誤,新增一個錯誤型別是用來代替屬性獲取可選鏈。

  • 如果沒有error,我們的JSON應該是這樣使用的。
var json:JSON = .....
let name = json.result?.list?[0].name?.string
複製程式碼

這種方式是利用可選鏈?來傳遞屬性,如果其中一個屬性不存在,不會報錯,而是得到一個nil值,一般情況這樣已經足夠,但如果出現錯誤的json,想要排查問題就需要逐層檢視或測試,錯誤資訊在可選鏈的第幾層完全丟失。

現在加入了error (Error, ignore:[String])型別,就無需使用可選鏈來傳遞屬性了

var json:JSON = .....
let name = json.result.list[0].name.string
複製程式碼

當某個屬性無法獲取時,可以給Error型別,而且還可以傳遞下去,並且不會丟失錯誤資訊。最終轉換string或者int時,可以根據開發環境和生產環境,選擇中斷或者返回預設值!

JSONDecoder 和 JSON.Decoder 的區別

  • JSONDecoder 是蘋果官方提供的將二進位制JSON解析成模型Model的解析類
  • JSON.Decoder是(Basic.frameworks)中仿照官方方法,將二進位制或JSON軟模型解析成模型Model的解析類

JSON解析關係圖
如圖所示,不同線條樣式代表不同的資料流向來源

  1. 他們的作用相似,只是JSON.Decoder削弱了資料型別的強制要求,
  2. JSON.Decoder提供了比官方JSONDecoder更多的解析策略,
  3. JSON.Decoder為了方便提供了全域性策略預設值的修改。

流向總結

  1. DataString可以通過官方的JSONDecoder或(Basic.frameworks)中的JSON.Decoder解析到實現Codable協議的Model模型
  2. DataString可以通過(Basic.frameworks)中的JSON.Decoder或解析到JSON軟模型、Model模型或兩者混合
  3. JSON軟模型可以通過(Basic.frameworks)中的JSON.Decoder解析到Model模型或混合模型。
  4. Model模型或混合模型可以通過(Basic.frameworks)中的JSON.Encoder系列化成JSON軟模型,或Data二進位制
  5. Model模型或混合模型可以通過官方的JSONEncoder系列化成Data二進位制
  6. JSON軟模型可以通過官方的JSONEncoder或(Basic.frameworks)中的JSON.Encoder系列化成Data二進位制

實際使用例項

參見文章《純Swift專案-HTTP(Basic.frameworks)》一文中的例子

相關文章