Swift JSON 發展史
- 最開始的時候還是使用
NSJSONSerialization
轉成字典和陣列來使用!- 後來蘋果用
Swift
重新實現了JSONSerialization
可以避免用NSArray
和NSDictionary
來橋接,提高解析效率。- 隨後很多三方
JSON
庫相繼出現,例如:SwiftyJSON
、HandyJSON
......等,請原諒我一直沒有用過這些三方庫,雖然有參考學習過,但我一直維護改進自己封裝的JSON庫,這期間雖然使用JSON
的簡便性有所提高,但我仍然覺得很麻煩。- 從
Swift 4.0
開始,蘋果提供了Codable
協議和JSONDecoder
,JSONEncoder
,這一改進使得一眾三方庫黯然失色,將JSON
到模型的便捷方式提升到了一個新的高度,但這種方式還是過於強硬,模型型別過於死板,稍有不慎就會因為型別不符導致解析失敗,因此我將自己的JSON
庫新增了Codable
協議支援,作為官方庫的一個有益補充。- 在
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
屬性的型別沒法填,因為無法確定此刻它該使用什麼模型。
解決方法如下:
- 不使用模型,改用字典,但這樣就回歸原始,用起來不方便。
result
屬性型別使用[String:Any]
,雖然當下使用了模型,但後面使用很不方便。ResponseJSON
使用泛型定義由呼叫網路請求介面預傳入result
所需的模型- 定義一種動態型別儲存未完全解析的
JSON
,容許對應介面使用result
欄位時懶解析到恰當的模型
其中,比較好的方法是3
和4
, 但方法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
)中object
、array
、string
、number
、bool
、null
這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.Decoder
削弱了資料型別的強制要求,JSON.Decoder
提供了比官方JSONDecoder
更多的解析策略,JSON.Decoder
為了方便提供了全域性策略預設值的修改。
流向總結
Data
或String
可以通過官方的JSONDecoder
或(Basic.frameworks)中的JSON.Decoder
解析到實現Codable
協議的Model
模型Data
或String
可以通過(Basic.frameworks)中的JSON.Decoder
或解析到JSON
軟模型、Model
模型或兩者混合JSON
軟模型可以通過(Basic.frameworks)中的JSON.Decoder
解析到Model
模型或混合模型。Model
模型或混合模型可以通過(Basic.frameworks)中的JSON.Encoder
系列化成JSON
軟模型,或Data
二進位制Model
模型或混合模型可以通過官方的JSONEncoder
系列化成Data
二進位制JSON
軟模型可以通過官方的JSONEncoder
或(Basic.frameworks)中的JSON.Encoder
系列化成Data
二進位制
實際使用例項
參見文章《純Swift專案-HTTP(Basic.frameworks)》一文中的例子