SwiftyJSON Github 上 Star 最多的 Swift JSON 解析框架git
ObjectMapper 面向協議的 Swift JSON 解析框架github
HandyJSON 阿里推出的一個用於 Swift 語言中的 JSON 序列化/反序列化庫。swift
JSONDecoder Apple 官方推出的基於 Codable
的 JSON 解析類數組
SwiftyJSON 採用下標方式獲取數據,使用起來比較麻煩,還容易發生拼寫錯誤、維護困難等問題。服務器
ObjectMapper 使用上相似 Codable
,可是須要額外寫 map 方法,重複勞動過多。微信
HandyJSON 使用上相似於 YYModel
,採用的是 Swift 反射 + 內存賦值的方式來構造 Model 實例。可是有內存泄露,兼容性差等問題。數據結構
Codable 是 Apple 官方提供的,更可靠,對原生類型支持更好。app
Codable
是 Swift 4.0 之後推出的一個編解碼協議,能夠配合 JSONDecoder
和 JSONEncoder
用來進行 JSON 解碼和編碼。框架
struct Foo: Codable {
let bar: Int
enum CodingKeys: String, CodingKey {
// key 映射
case bar = "rab"
}
init(from decoder: Decoder) throws {
// 自定義解碼
let container = try decoder.container(keyedBy: CodingKeys.self)
if let intValue = try container.decodeIfPresent(String.self, forKey: .bar) {
self.bar = intValue
} else {
self.bar = try container.decode(Int.self, forKey: .bar)
}
}
let decoder = JSONDecoder()
try decoder.decode(Foo.self, from: data)
// 蛇形命名轉駝峯
decoder.keyDecodingStrategy = .convertFromSnakeCase
// 日期解析使用 UNIX 時間戳
decoder.dateDecodingStrategy = .secondsSince1970
複製代碼
只要有一個屬性解析失敗則直接拋出異常致使整個解析過程失敗。ide
如下狀況均會解析失敗:
後兩個能夠經過使用可選類型避免,第一種狀況只能重寫協議方法來規避,可是很難徹底避免。而使用可選類型勢必會有大量的可選綁定,對於 enum
和 Bool
來講使用可選類型是很是痛苦的,並且這些都會增長代碼量。這時候就須要一種解決方案來解決這些痛點。
// 入口方法
JSONDecoder decode<T : Decodable>(_ type: T.Type, from data: Data)
// 內部私有類,實際用來解析的
__JSONDecoder unbox<T : Decodable>(_ value: Any, as type: T.Type)
// 遵循 Decodable 協議的類調用協議方法
Decodable init(from decoder: Decoder)
// 自動生成的 init 方法調用 container
Decoder container(keyedBy: CodingKeys)
// 解析的容器
KeyedDecodingContainer decodeIfPresent(type: Type) or decode(type: Type)
// 內部私有類,循環調用 unbox
__JSONDecoder unbox(value:Any type:Type)
...循環,直到基本類型
複製代碼
JSONDecoder
內部其實是使用 __JSONDecoder
這個私有類來進行解碼的,最終都是調用 unbox
方法。
如下代碼摘自 Swift 標準庫源碼,分別是解碼 Bool
和 Int
類型,能夠看到一旦解析失敗直接拋出異常,沒有容錯機制。
fileprivate func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? {
guard !(value is NSNull) else { return nil }
if let number = value as? NSNumber {
// TODO: Add a flag to coerce non-boolean numbers into Bools?
if number === kCFBooleanTrue as NSNumber {
return true
} else if number === kCFBooleanFalse as NSNumber {
return false
}
/* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested: } else if let bool = value as? Bool { return bool */
}
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? {
guard !(value is NSNull) else { return nil }
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let int = number.intValue
guard NSNumber(value: int) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return int
}
複製代碼
因爲 __JSONDecoder
是內部私有類,而 Decoder
協議暴露的接口太少,鑑於 Swift protocol extension
優先使用當前模塊的協議方法,因此能夠從 KeyedDecodingContainer
協議下手。
所以 初版解決方案 誕生了。經過擴展 KeyedDecodingContainer
協議,重寫 decodeIfPresent
和 decode
方法,捕獲異常並處理。若是是可選類型則將異常拋出改成返回 nil
,若是是不可選類型則返回默認值。
缺點:
繼承自 JSONDecoder,在標準庫源碼基礎上作了改動,以解決 JSONDecoder 各類解析失敗的問題,如鍵值不存在,值爲 null,類型不一致。
從標準庫複製一份源碼
在最底層的 unbox
方法裏面將異常拋出改成返回 nil
在 SingleValueDecodingContainer
和 KeyedDecodingContainerProtocol
協議方法中經過 KeyNotFoundDecodingStrategy
和 ValueNotFoundDecodingStrategy
兩種策略處理異常,並經過 JSONAdapter
協議提供自定義適配方法。
對於枚舉這種沒法肯定默認值的類型,提供一個 CaseDefaultable
協議,而後重寫 init(from decoder: Decoder)
方法來處理異常。
nil
或者默認值將 JSONDecoder
替換成 CleanJSONDecoder
便可。
let decoder = CleanJSONDecoder()
try decoder.decode(Foo.self, from: data)
複製代碼
對於不可選的枚舉類型請遵循 CaseDefaultable
協議,若是解析失敗會返回默認 case
NOTE:枚舉使用強類型解析,關聯類型和數據類型不一致不會進行類型轉換,會解析爲默認 case
enum Enum: Int, Codable, CaseDefaultable {
case case1
case case2
case case3
static var defaultCase: Enum {
return .case1
}
}
複製代碼
能夠經過 valueNotFoundDecodingStrategy
在值爲 null 或類型不匹配的時候自定義解碼。
struct CustomAdapter: JSONAdapter {
// 因爲 Swift 布爾類型不是非 0 即 true,因此默認沒有提供類型轉換。
// 若是想實現 Int 轉 Bool 能夠自定義解碼。
func adapt(_ decoder: CleanDecoder) throws -> Bool {
// 值爲 null
if decoder.decodeNil() {
return false
}
if let intValue = try decoder.decodeIfPresent(Int.self) {
// 類型不匹配,指望 Bool 類型,實際是 Int 類型
return intValue != 0
}
return false
}
}
decoder.valueNotFoundDecodingStrategy = .custom(CustomAdapter())
複製代碼
以上是對不一樣數量級的數據解析對比。數據結構越複雜,性能差距會更大。
JSONSerialization
CleanJSON
HandyJSON
ObjectMapper
能夠看到 JSONSerialization
速度是最快的,但同時也是代碼量最多的,容錯處理最差的。CleanJSON
和 ObjectMapper
速度不相上下,但 ObjectMapper
代碼量較多,且對不可選類型的解析和 JSONDecoder
同樣解析失敗直接拋出異常。HandyJSON
性能較差。
引用 Mattt 大神的分析:
On average, Codable with JSONDecoder is about half as fast as the equivalent implementation with JSONSerialization.
But does this mean that we shouldn’t use Codable? Probably not.
A 2x speedup factor may seem significant, but measured in absolute time difference, the savings are unlikely to be appreciable under most circumstances — and besides, performance is only one consideration in making a successful app.