SwiftyJSON源码解析

2,536 阅读5分钟

SwiftyJSON 为json解析提供了优雅的解决方案,而且源代码并不多,其理念非常值得学习。

核心

SwiftJSON的核心数据结构是JSONJSON就像一个工厂,我们的数据就是原材料,当把原材料交给这个工厂之后,就可以向其索要任何我们想要的数据格式,工厂会为我们处理转化过程。

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 ,数据的获取和设置调用流程是一样的,调用流程如图:


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的拆解工作,实现的比较优雅。

使用JSONSubscriptType protocolJSONKey enum统一了key的类型,使IntString都可以作为key。 在fileprivate subscript(sub sub: JSONSubscriptType) -> JSON解析枚举类型,枚举在这里的使用形式也值得借鉴。

最终值的获取

通过 key 获取的类型最终还是被包装在了JSON结构体中,要真正取到值还得要调用下面的方法:

方法较多,但都是对相应的类型都提供可选值和非可选值的版本,使用起来非常方便。 来看看最常用的 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,对外界隐匿了复杂的中间值判断,使外界只需要关心最终值的类型即可。