ObjectMapper原始碼分析
在看ObjectMapper原始碼的時候,我自己嘗試著寫了一個簡易的JSON解析器。程式碼在DJJSON的ObjMapper裡。
首先我們都知道,使用ObjectMapper的時候,我們一定要實現Mappable協議。這個協議裡又有兩個要實現的方法:
init?(map: Map)
mutating func mapping(map: Map)
複製程式碼
使用的時候,只用如下:
struct Ability: Mappable {
var mathematics: String?
var physics: String?
var chemistry: String?
init?(map: Map) {
}
mutating func mapping(map: Map) {
mathematics <- map["mathematics"]
physics <- map["physics"]
chemistry <- map["chemistry"]
}
}
複製程式碼
然後對應的json如下:
let json = """
{
"mathematics": "excellent",
"physics": "bad",
"chemistry": "fine"
}
"""
複製程式碼
然後將這段json解析為Ability的Model, 即:
let ability = Mapper<Ability>().map(JSONString: json)
複製程式碼
這樣就完成了JSON資料的解析成Model的過程,這也是我們專案中最頻繁出現的場景。那麼,我們有想過,為什麼這樣就能將JSON的資料轉化為對應的Model麼?為什麼會需要有‘<-’這麼奇怪的符號,它又是啥意思呢?
首先初看<-的符號,我們的第一感覺就是把右邊的值賦給左邊的變數,然後我們去看原始碼,發現這個符號是這個庫自定義的一個操作符。在Operators.swift裡。
定義如下:
infix operator <-
/// Object of Basic type
public func <- <T>(left: inout T, right: Map) {
switch right.mappingType {
case .fromJSON where right.isKeyPresent:
FromJSON.basicType(&left, object: right.value())
case .toJSON:
left >>> right
default: ()
}
}
複製程式碼
然後根據不同的泛型型別,這個操作符會進行不同的處理。
接著,我們再看一下map方法。
map方法存在於Mapper類中, 定義如下:
func map(JSONString: String) -> M? {
if let JSON = Mapper.parseJSONString(JSONString: JSONString) as? [String: Any] {
return map(JSON: JSON)
}
return nil
}
func map(JSON: [String: Any]) -> M? {
let map = Map(JSON: JSON)
if let klass = M.self as? Mappable.Type {
if var obj = klass.init(map: map) as? M {
obj.mapping(map: map)
return obj
}
}
return nil
}
複製程式碼
可以看到,在map的方法中,我們最後會呼叫Mappable協議中定義的mapping方法,來對json資料做出轉化。
最後再看一下Map這個類,這個類主要用來處理找到key所對應的value。處理方式如下:
private func valueFor(_ keyPathComponents: ArraySlice<String>, dict: [String: Any]) -> (Bool, Any?) {
guard !keyPathComponents.isEmpty else { return (false, nil) }
if let keyPath = keyPathComponents.first {
let obj = dict[keyPath]
if obj is NSNull {
return (true, nil)
} else if keyPathComponents.count > 1, let d = obj as? [String: Any] {
let tail = keyPathComponents.dropFirst()
return valueFor(tail, dict: d)
} else if keyPathComponents.count > 1, let arr = obj as? [Any] {
let tail = keyPathComponents.dropFirst()
return valueFor(tail, array: arr)
} else {
return (obj != nil, obj)
}
}
return (false, nil)
}
private func valueFor(_ keyPathComponents: ArraySlice<String>, array: [Any]) -> (Bool, Any?) {
guard !keyPathComponents.isEmpty else { return (false, nil) }
if let keyPath = keyPathComponents.first, let index = Int(keyPath), index >= 0 && index < array.count {
let obj = array[index]
if obj is NSNull {
return (true, nil)
} else if keyPathComponents.count > 1, let dict = obj as? [String: Any] {
let tail = keyPathComponents.dropFirst()
return valueFor(tail, dict: dict)
} else if keyPathComponents.count > 1, let arr = obj as? [Any] {
let tail = keyPathComponents.dropFirst()
return valueFor(tail, array: arr)
} else {
return (true, obj)
}
}
return (false, nil)
}
複製程式碼
其中在處理分隔符上,採用的是遞迴呼叫的方式,不過就我們目前專案中,還沒有用到過。
上述這幾個步驟,就是ObjectMapper的核心方法。我也根據這些步驟,自己實現了一個解析的庫。
但是這個只能解析一些最簡單的型別,其他的像enum之類的,還需要做一些自定義的轉化。主要的資料轉化都在Operators資料夾中。
SwiftyJSON 原始碼解析
構造器
SwiftyJSON對外暴露的主要的構造器:
public init(data: Data, options opt: JSONSerialization.ReadingOptions = []) throws
public init(_ object: Any)
public init(parseJSON jsonString: String)
複製程式碼
最終呼叫的構造器為:
fileprivate init(jsonObject: Any)
複製程式碼
型別
自定義了幾個型別:
public enum Type: Int {
case number
case string
case bool
case array
case dictionary
case null
case unknown
}
複製程式碼
儲存物件已經何時對JSON進行的解析
-
儲存物件:
/// Private object fileprivate var rawArray: [Any] = [] fileprivate var rawDictionary: [String: Any] = [:] fileprivate var rawString: String = "" fileprivate var rawNumber: NSNumber = 0 fileprivate var rawNull: NSNull = NSNull() fileprivate var rawBool: Bool = false /// JSON type, fileprivate setter public fileprivate(set) var type: Type = .null /// Error in JSON, fileprivate setter public fileprivate(set) var error: SwiftyJSONError? 複製程式碼
-
解析過程
主要是在object屬性的get & set方法中進行。然後將解析後的值儲存到上述的屬性中去。解析過程中,有個unwrap方法值得我們關注。
unwrap:
/// Private method to unwarp an object recursively private func unwrap(_ object: Any) -> Any { switch object { case let json as JSON: return unwrap(json.object) case let array as [Any]: return array.map(unwrap) case let dictionary as [String: Any]: var unwrappedDic = dictionary for (k, v) in dictionary { unwrappedDic[k] = unwrap(v) } return unwrappedDic default: return object } } 複製程式碼
這個方法根據object的型別,對其進行遞迴的解析。
JSON的語法糖
為了統一Array和Dictionary的下標訪問的型別,自定義了一個enum型別,JSONKey:
public enum JSONKey {
case index(Int)
case key(String)
}
// To mark both String and Int can be used in subscript.
extension Int: JSONSubscriptType {
public var jsonKey: JSONKey {
return JSONKey.index(self)
}
}
extension String: JSONSubscriptType {
public var jsonKey: JSONKey {
return JSONKey.key(self)
}
}
複製程式碼
然後就是喜聞樂見的subscript語法糖:
```
let json = JSON[data]
let path = [9,"list","person","name"]
let name = json[path]
```
public subscript(path: [JSONSubscriptType]) -> JSON
// let name = json[9]["list"]["person"]["name"]
public subscript(path: JSONSubscriptType...) -> JSON
複製程式碼
資料的轉化
最後就是暴露欄位,給開發者使用。比如:
public var int: Int?
public var intValue: Int
複製程式碼
每個型別都有optional和unoptional。
Swift 4.0及以後的JSON解析
首先我們知道,在Swift4.0以前,JSON資料解析成Model是多麼的繁瑣。舉個例子:
/// Swift 3.0
if let data = json.data(using: .utf8, allowLossyConversion: true),
let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
print("name: \(dict["name"])")
}
/// 我們只能獲取到json對應的dict,至於轉換成model的話,還是需要採用上面的方式,本質都是遞迴轉化,即key與property的對應轉化。
複製程式碼
那麼,在swift4.0後,我們可以怎麼做呢。如下:
struct DJJSON<T: Decodable> {
fileprivate let data: Data
init(data: Data?) {
if let data = data {
self.data = data
} else {
self.data = Data()
}
}
init(jsonString: String) {
let data = jsonString.data(using: .utf8, allowLossyConversion: true)
self.init(data: data)
}
func decode() -> T? {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(T.self, from: data)
return result
} catch let error {
print("error: \(error)")
}
return nil
}
}
if let r = DJJSON<GrocerProduct>(jsonString: json).decode() {
print("result: \(r.ability?.mathematics)")
print("imageUrl: \(r.imageUrl)")
}
複製程式碼
我們只要保證轉化的model是遵守Codable協議的即可。至於Key跟Property的轉化,蘋果預設就幫我做了。那麼有的朋友就要問了,那怎麼自定義Key呢,蘋果給我們提供了一個enum叫CodingKeys, 我們只要在這個裡面做自定義就行了,預設的話就是key與property是對應的。如:
private enum CodingKeys: String, CodingKey {
case mathematics = "math"
case physics, chemistry
}
複製程式碼
那麼問題又來了,有些欄位是蛇形的,像什麼image_url,有沒有辦法不自己做自定義就能搞定呢,誒,還真有,那就是在swift4.1中提供的這個convertFromSnakeCase。
// 完成image_url與imageUrl的轉化
decoder.keyDecodingStrategy = .convertFromSnakeCase
複製程式碼
那麼,這個是怎麼實現的呢,我們很好奇,因為感覺自己也可以做這個轉化啊,是不是easy game。我們去看swift的原始碼:
fileprivate static func _convertFromSnakeCase(_ stringKey: String) -> String {
guard !stringKey.isEmpty else { return stringKey }
// Find the first non-underscore character
guard let firstNonUnderscore = stringKey.index(where: { $0 != "_" }) else {
// Reached the end without finding an _
return stringKey
}
// Find the last non-underscore character
var lastNonUnderscore = stringKey.index(before: stringKey.endIndex)
while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" {
stringKey.formIndex(before: &lastNonUnderscore)
}
let keyRange = firstNonUnderscore...lastNonUnderscore
let leadingUnderscoreRange = stringKey.startIndex..<firstNonUnderscore
let trailingUnderscoreRange = stringKey.index(after: lastNonUnderscore)..<stringKey.endIndex
var components = stringKey[keyRange].split(separator: "_")
let joinedString : String
if components.count == 1 {
// No underscores in key, leave the word as is - maybe already camel cased
joinedString = String(stringKey[keyRange])
} else {
joinedString = ([components[0].lowercased()] + components[1...].map { $0.capitalized }).joined()
}
// Do a cheap isEmpty check before creating and appending potentially empty strings
let result : String
if (leadingUnderscoreRange.isEmpty && trailingUnderscoreRange.isEmpty) {
result = joinedString
} else if (!leadingUnderscoreRange.isEmpty && !trailingUnderscoreRange.isEmpty) {
// Both leading and trailing underscores
result = String(stringKey[leadingUnderscoreRange]) + joinedString + String(stringKey[trailingUnderscoreRange])
} else if (!leadingUnderscoreRange.isEmpty) {
// Just leading
result = String(stringKey[leadingUnderscoreRange]) + joinedString
} else {
// Just trailing
result = joinedString + String(stringKey[trailingUnderscoreRange])
}
return result
}
複製程式碼
真的寫的特別精煉跟嚴謹好吧,學習一下這個。
到這裡,就結束了,謝謝大家。