更多文章git
在看ObjectMapper源碼的時候,我本身嘗試着寫了一個簡易的JSON解析器。代碼在DJJSON的ObjMapper裏。github
首先咱們都知道,使用ObjectMapper的時候,咱們必定要實現Mappable協議。這個協議裏又有兩個要實現的方法:json
init?(map: Map)
mutating func mapping(map: Map)
複製代碼
使用的時候,只用以下:swift
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以下:api
let json = """
{
"mathematics": "excellent",
"physics": "bad",
"chemistry": "fine"
}
"""
複製代碼
而後將這段json解析爲Ability的Model, 即:app
let ability = Mapper<Ability>().map(JSONString: json)
複製代碼
這樣就完成了JSON數據的解析成Model的過程,這也是咱們項目中最頻繁出現的場景。那麼,咱們有想過,爲何這樣就能將JSON的數據轉化爲對應的Model麼?爲何會須要有‘<-’這麼奇怪的符號,它又是啥意思呢?源碼分析
首先初看<-的符號,咱們的第一感受就是把右邊的值賦給左邊的變量,而後咱們去看源碼,發現這個符號是這個庫自定義的一個操做符。在Operators.swift裏。學習
定義以下:url
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: ()
}
}
複製代碼
而後根據不一樣的泛型類型,這個操做符會進行不一樣的處理。spa
接着,咱們再看一下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對外暴露的主要的構造器:
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
}
複製代碼
存儲對象:
/// 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的類型,對其進行遞歸的解析。
爲了統一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。
首先咱們知道,在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
}
複製代碼
真的寫的特別精煉跟嚴謹好吧,學習一下這個。
到這裏,就結束了,謝謝你們。