SwiftyJSON源碼解析

SwiftyJSON 爲json解析提供了優雅的解決方案,並且源代碼並很少,其理念很是值得學習。json

核心

SwiftJSON的核心數據結構是JSONJSON就像一個工廠,咱們的數據就是原材料,當把原材料交給這個工廠以後,就能夠向其索要任何咱們想要的數據格式,工廠會爲咱們處理轉化過程。swift

JSON的成員構成

public enum Type: Int {
	case number
	case string
	case bool
	case array
	case dictionary
	case null
	case unknown
}

public Struct JSON {
 
     /// 具體類型的存儲成員
    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

    /// 類型信息
    public fileprivate(set) var type: Type = .null

    /// 發生錯誤後的存儲成員
    public fileprivate(set) var error: SwiftyJSONError?
    
      /// 數據的存儲成員,這個傢伙很重要
    public var object: Any {
        ....
    }
 }
複製代碼

JSON保存了數據的類型和原數據這兩個重要的信息,接下來的解析過程都是以這兩個成員爲基礎。數組

json解析工廠的初始化

SwiftyJSON對外提供了三個初始化器,這三個初始化器都試圖先將數據轉換爲Data,最終會來到fileprivate init(jsonObject: Any)方法中。數據結構

fileprivate init(jsonObject: Any) {
        // 觸發object 成員setter
        object = jsonObject
 }
 
 
public var object: Any {
        get {
           ...
        }
        
        //解析數據的類型,填充具體類型的成員
        set {
            error = nil
            switch unwrap(newValue) {
            case let number as NSNumber:
                if number.isBool {
                    type = .bool
                    rawBool = number.boolValue
                } else {
                    type = .number
                    rawNumber = number
                }
            case let string as String:
                type = .string
                rawString = string
            case _ as NSNull:
                type = .null
            case nil:
                type = .null
            case let array as [Any]:
                type = .array
                rawArray = array
            case let dictionary as [String: Any]:
                type = .dictionary
                rawDictionary = dictionary
            default:
                type = .unknown
                error = SwiftyJSONError.unsupportedType
            }
        }
    }
    
//遞歸的解析數據的類型 
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 d = dictionary
        dictionary.forEach { pair in
            d[pair.key] = unwrap(pair.value)
        }
        return d
    default:
        return object
    }
}
複製代碼

小結: 初始化過程首先保存的數據的副本,而且解析根對象的類型並保存,而且填充具體類型的成員學習

數據節點獲取與設置

SwiftyJOSN 獲取值的調用方式支持subscript,與直接操做Dictionary體驗一致。對外提供的公有接口是public subscript(path: JSONSubscriptType...) -> JSON ,數據的獲取和設置調用流程是同樣的,調用流程如圖:ui

public enum JSONKey {
    case index(Int)
    case key(String)
}

public protocol JSONSubscriptType {
    var jsonKey: JSONKey { get }
}

extension Int: JSONSubscriptType {
    public var jsonKey: JSONKey {
        return JSONKey.index(self)
    }
}

extension String: JSONSubscriptType {
    public var jsonKey: JSONKey {
        return JSONKey.key(self)
    }
}

extension JSON {

    /// 解析到當前操做的類型是array類型
    fileprivate subscript(index index: Int) -> JSON {
       //從array類型中取值
        get {
            if type != .array { 
                //處理類型錯誤
                var r = JSON.null
                r.error = self.error ?? SwiftyJSONError.wrongType
                return r
            } else if rawArray.indices.contains(index) {
               //對外只返回JSON類型
                return JSON(rawArray[index])
            } else {
               //處理數組索引錯誤
                var r = JSON.null
                r.error = SwiftyJSONError.indexOutOfBounds
                return r
            }
        }
        //向array類型中設置值
        set {
            if type == .array &&
                rawArray.indices.contains(index) &&
                newValue.error == nil {
                rawArray[index] = newValue.object
            }
        }
    }

    /// 解析到當前操做的類型是dictionary類型
    fileprivate subscript(key key: String) -> JSON {
       //從dictionary中取值
        get {
            var r = JSON.null
            if type == .dictionary {
                if let o = rawDictionary[key] {
                    //包裝對象對外只返回JSON
                    r = JSON(o)
                } else {
                    //不存在
                    r.error = SwiftyJSONError.notExist
                }
            } else {
                //類型錯誤
                r.error = self.error ?? SwiftyJSONError.wrongType
            }
            return r
        }
        set {
            if type == .dictionary && newValue.error == nil {
                rawDictionary[key] = newValue.object
            }
        }
    }

    /// 對key的類型進行解析,進而決定是從array中獲取仍是從json中獲取
    fileprivate subscript(sub sub: JSONSubscriptType) -> JSON {
        get {
            switch sub.jsonKey {
            case .index(let index): return self[index: index]
            case .key(let key):     return self[key: key]
            }
        }
        set {
            switch sub.jsonKey {
            case .index(let index): self[index: index] = newValue
            case .key(let key):     self[key: key] = newValue
            }
        }
    }

  
    public subscript(path: [JSONSubscriptType]) -> JSON {
        get {
           //解析path 的key,一層一層獲取
            return path.reduce(self) { $0[sub: $1] }
        }
        set {
            switch path.count {
            case 0: return
            case 1: self[sub:path[0]].object = newValue.object
            default:
                var aPath = path
                aPath.remove(at: 0)
                //遞歸地設置值,先去就舊值修改,而後再設置回去,直到path的最後一個key
                var nextJSON = self[sub: path[0]]
                nextJSON[aPath] = newValue
                self[sub: path[0]] = nextJSON
            }
        }
    }

    //對外暴露的接口
    public subscript(path: JSONSubscriptType...) -> JSON {
        get {
            return self[path]
        }
        set {
            self[path] = newValue
        }
    }
}
複製代碼

public subscript(path: [JSONSubscriptType]) -> JSON方法完成了對paths的拆解工做,實現的比較優雅。spa

使用JSONSubscriptType protocolJSONKey enum統一了key的類型,使IntString均可以做爲key。 在fileprivate subscript(sub sub: JSONSubscriptType) -> JSON解析枚舉類型,枚舉在這裏的使用形式也值得借鑑。code

最終值的獲取

經過 key 獲取的類型最終仍是被包裝在了JSON結構體中,要真正取到值還得要調用下面的方法:cdn

方法較多,但都是對相應的類型都提供可選值和非可選值的版本,使用起來很是方便。 來看看最經常使用的 Number類型 和 String類型的實現原理對象

獲取 Number

//Optional number
    public var number: NSNumber? {
        get {
            switch type {
            case .number: return rawNumber
            case .bool:   return NSNumber(value: rawBool ? 1 : 0)
            default:      return nil
            }
        }
        set {
            object = newValue ?? NSNull()
        }
    }

    //Non-optional number
    public var numberValue: NSNumber {
        get {
            switch type {
            case .string:
                let decimal = NSDecimalNumber(string: object as? String)
                return decimal == .notANumber ? .zero : decimal
            case .number: return object as? NSNumber ?? NSNumber(value: 0)
            case .bool: return NSNumber(value: rawBool ? 1 : 0)
            default: return NSNumber(value: 0.0)
            }
        }
        set {
            object = newValue
        }
    }
複製代碼

獲取String

extension JSON {

    //Optional string
    public var string: String? {
        get {
            switch type {
            case .string: return object as? String
            default:      return nil
            }
        }
        set {
            object = newValue ?? NSNull()
        }
    }

    //Non-optional string
    public var stringValue: String {
        get {
            switch type {
            case .string: return object as? String ?? ""
            case .number: return rawNumber.stringValue
            case .bool:   return (object as? Bool).map { String($0) } ?? ""
            default:      return ""
            }
        }
        set {
            object = newValue
        }
    }
}
複製代碼

這兩個方法的實現大同小異,都是先類型解析,而後包裝值類型。 而值的類型都是在初始化時解析完成的。

總結

SwiftyJSON 代碼看下來,並無什麼難懂的概念,可是實現確很優雅,可見做者的功力。

SwiftyJSON核心思想總結爲八個字:「統一類型,取值包裝」。 使用核心類型JSON包裝json數據,在取值過程當中不斷的將中間值包裝到JSON,對外界隱匿了複雜的中間值判斷,使外界只須要關心最終值的類型便可。

相關文章
相關標籤/搜索