ObjectMapper分析与学习二

核心实现

Map

/// MapContext is available for developers who wish to pass information around during the mapping process.
public protocol MapContext {
    
}

/// A class used for holding mapping data
public final class Map {
    //枚举
    public let mappingType: MappingType
    //表示set方法只有在内部模块才能访问
    public internal(set) var JSON: [String: Any] = [:]
    public internal(set) var isKeyPresent = false
    public internal(set) var currentValue: Any?
    public internal(set) var currentKey: String?
    //嵌套的
    var keyIsNested = false
    //嵌套key的分解符
    public internal(set) var nestedKeyDelimiter: String = "."
    //
    public var context: MapContext?
    //是否包含nil值
    public var shouldIncludeNilValues = false  /// If this is set to true, toJSON output will include null values for any variables that are not set.
    
    let toObject: Bool // indicates whether the mapping is being applied to an existing object
    
    public init(mappingType: MappingType, JSON: [String: Any], toObject: Bool = false, context: MapContext? = nil, shouldIncludeNilValues: Bool = false) {
        
        self.mappingType = mappingType
        self.JSON = JSON
        self.toObject = toObject
        self.context = context
        self.shouldIncludeNilValues = shouldIncludeNilValues
    }
    
    //根据key 寻值
    /// Sets the current mapper value and key.
    /// The Key paramater can be a period separated string (ex. "distance.value") to access sub objects.
    public subscript(key: String) -> Map {
        // save key and value associated to it
        return self[key, delimiter: ".", ignoreNil: false]
    }
    
    public subscript(key: String, delimiter delimiter: String) -> Map {
        let nested = key.contains(delimiter)
        return self[key, nested: nested, delimiter: delimiter, ignoreNil: false]
    }
    
    public subscript(key: String, nested nested: Bool) -> Map {
        return self[key, nested: nested, delimiter: ".", ignoreNil: false]
    }
    
    public subscript(key: String, nested nested: Bool, delimiter delimiter: String) -> Map {
        return self[key, nested: nested, delimiter: delimiter, ignoreNil: false]
    }
    
    public subscript(key: String, ignoreNil ignoreNil: Bool) -> Map {
        return self[key, delimiter: ".", ignoreNil: ignoreNil]
    }
    
    public subscript(key: String, delimiter delimiter: String, ignoreNil ignoreNil: Bool) -> Map {
        let nested = key.contains(delimiter)
        return self[key, nested: nested, delimiter: delimiter, ignoreNil: ignoreNil]
    }
    
    public subscript(key: String, nested nested: Bool, ignoreNil ignoreNil: Bool) -> Map {
        return self[key, nested: nested, delimiter: ".", ignoreNil: ignoreNil]
    }
    
    public subscript(key: String, nested nested: Bool, delimiter delimiter: String, ignoreNil ignoreNil: Bool) -> Map {
        // save key and value associated to it
        currentKey = key
        keyIsNested = nested
        nestedKeyDelimiter = delimiter
        
        if mappingType == .fromJSON {
            // check if a value exists for the current key
            // do this pre-check for performance reasons
            if nested == false {
                let object = JSON[key]
                let isNSNull = object is NSNull
                isKeyPresent = isNSNull ? true : object != nil
                currentValue = isNSNull ? nil : object
            } else {
                // break down the components of the key that are separated by .
                //为key找到正确的值,自定义的寻值方式,需要标记是否有嵌套
                (isKeyPresent, currentValue) = valueFor(ArraySlice(key.components(separatedBy: delimiter)), dictionary: JSON)
            }
            
            // update isKeyPresent if ignoreNil is true
            if ignoreNil && currentValue == nil {
                isKeyPresent = false
            }
        }
        
        return self
    }
    
    public func value<T>() -> T? {
        return currentValue as? T
    }
    
}

/// Fetch value from JSON dictionary, loop through keyPathComponents until we reach the desired object
private func valueFor(_ keyPathComponents: ArraySlice<String>, dictionary: [String: Any]) -> (Bool, Any?) {
    // Implement it as a tail recursive function.
    if keyPathComponents.isEmpty {
        return (false, nil)
    }
    
    if let keyPath = keyPathComponents.first {
        let object = dictionary[keyPath]
        if object is NSNull {
            return (true, nil)
        } else if keyPathComponents.count > 1, let dict = object as? [String: Any] {
            let tail = keyPathComponents.dropFirst()
            return valueFor(tail, dictionary: dict)
        } else if keyPathComponents.count > 1, let array = object as? [Any] {
            let tail = keyPathComponents.dropFirst()
            return valueFor(tail, array: array)
        } else {
            return (object != nil, object)
        }
    }
    
    return (false, nil)
}

/// Fetch value from JSON Array, loop through keyPathComponents them until we reach the desired object
private func valueFor(_ keyPathComponents: ArraySlice<String>, array: [Any]) -> (Bool, Any?) {
    // Implement it as a tail recursive function.
    
    if keyPathComponents.isEmpty {
        return (false, nil)
    }
    
    //Try to convert keypath to Int as index
    if let keyPath = keyPathComponents.first,
        let index = Int(keyPath) , index >= 0 && index < array.count {
        
        let object = array[index]
        
        if object is NSNull {
            return (true, nil)
        } else if keyPathComponents.count > 1, let array = object as? [Any]  {
            let tail = keyPathComponents.dropFirst()
            return valueFor(tail, array: array)
        } else if  keyPathComponents.count > 1, let dict = object as? [String: Any] {
            let tail = keyPathComponents.dropFirst()
            return valueFor(tail, dictionary: dict)
        } else {
            return (true, object)
        }
    }
    
    return (false, nil)
}

MapError

错误输出的格式和内容

Mapper

public enum MappingType {
    case fromJSON
    case toJSON
}

/// The Mapper class provides methods for converting Model objects to JSON and methods for converting JSON to Model objects
public final class Mapper<N: BaseMappable> {
    
    public var context: MapContext?
    public var shouldIncludeNilValues = false /// If this is set to true, toJSON output will include null values for any variables that are not set.
    
    public init(context: MapContext? = nil, shouldIncludeNilValues: Bool = false){
        self.context = context
        self.shouldIncludeNilValues = shouldIncludeNilValues
    }
    
    // MARK: Mapping functions that map to an existing object toObject
    //json对象像已存在的object赋值
    /// Maps a JSON object to an existing Mappable object if it is a JSON dictionary, or returns the passed object as is
    public func map(JSONObject: Any?, toObject object: N) -> N {
        if let JSON = JSONObject as? [String: Any] {
            return map(JSON: JSON, toObject: object)
        }
        
        return object
    }
    
    /// Map a JSON string onto an existing object
    //json字符串像已存在的object赋值
    public func map(JSONString: String, toObject object: N) -> N {
        if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) {
            return map(JSON: JSON, toObject: object)
        }
        return object
    }
    
    /// Maps a JSON dictionary to an existing object that conforms to Mappable.
    /// Usefull for those pesky objects that have crappy designated initializers like NSManagedObject
    public func map(JSON: [String: Any], toObject object: N) -> N {
        var mutableObject = object
        //map初始化
        let map = Map(mappingType: .fromJSON, JSON: JSON, toObject: true, context: context, shouldIncludeNilValues: shouldIncludeNilValues)
        //通过协议在model中实现
        mutableObject.mapping(map: map)
        return mutableObject
    }

    //MARK: Mapping functions that create an object
    
    /// Map a JSON string to an object that conforms to Mappable
    //
    public func map(JSONString: String) -> N? {
        //转字典
        if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) {
            return map(JSON: JSON)
        }
        
        return nil
    }
    
    /// Maps a JSON object to a Mappable object if it is a JSON dictionary or NSString, or returns nil.
    public func map(JSONObject: Any?) -> N? {
        if let JSON = JSONObject as? [String: Any] {
            return map(JSON: JSON)
        }

        return nil
    }

    /// Maps a JSON dictionary to an object that conforms to Mappable
    public func map(JSON: [String: Any]) -> N? {
        //map初始化
        let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues)
        
        if let klass = N.self as? StaticMappable.Type { // Check if object is StaticMappable
            if var object = klass.objectForMapping(map: map) as? N {
                object.mapping(map: map)
                return object
            }
        } else if let klass = N.self as? Mappable.Type { // Check if object is Mappable
            if var object = klass.init(map: map) as? N {
                object.mapping(map: map)
                return object
            }
        } else if let klass = N.self as? ImmutableMappable.Type { // Check if object is ImmutableMappable
            do {
                return try klass.init(map: map) as? N
            } catch let error {
                #if DEBUG
                let exception: NSException
                if let mapError = error as? MapError {
                    exception = NSException(name: .init(rawValue: "MapError"), reason: mapError.description, userInfo: nil)
                } else {
                    exception = NSException(name: .init(rawValue: "ImmutableMappableError"), reason: error.localizedDescription, userInfo: nil)
                }
                exception.raise()
                #else
                NSLog("\(error)")
                #endif
            }
        } else {
            // Ensure BaseMappable is not implemented directly
            assert(false, "BaseMappable should not be implemented directly. Please implement Mappable, StaticMappable or ImmutableMappable")
        }
        
        return nil
    }

Mappable

对外核心功能接口,遵守Mappable协议以后,对象就可以直接调用类方法实现对象与json的相互转换

/// BaseMappable should not be implemented directly. Mappable or StaticMappable should be used instead
public protocol BaseMappable {
    /// This function is where all variable mappings should occur. It is executed by Mapper during the mapping (serialization and deserialization) process.
    //使用 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量,在设计接口的时候,也要考虑到使用者程序的扩展性。
    mutating func mapping(map: Map)
}

public protocol Mappable: BaseMappable {
    /// This function can be used to validate JSON prior to mapping. Return nil to cancel mapping at this point
    init?(map: Map)
}

public protocol StaticMappable: BaseMappable {
    /// This is function that can be used to:
    ///     1) provide an existing cached object to be used for mapping
    ///     2) return an object of another class (which conforms to BaseMappable) to be used for mapping. For instance, you may inspect the JSON to infer the type of object that should be used for any given mapping
    static func objectForMapping(map: Map) -> BaseMappable?
}

//扩展实现对象转json或json转对象
public extension BaseMappable {
    
    /// Initializes object from a JSON String
    public init?(JSONString: String, context: MapContext? = nil) {
        if let obj: Self = Mapper(context: context).map(JSONString: JSONString) {
            self = obj
        } else {
            return nil
        }
    }
    
    /// Initializes object from a JSON Dictionary
    public init?(JSON: [String: Any], context: MapContext? = nil) {
        if let obj: Self = Mapper(context: context).map(JSON: JSON) {
            self = obj
        } else {
            return nil
        }
    }
    
    /// Returns the JSON Dictionary for the object
    public func toJSON() -> [String: Any] {
        return Mapper().toJSON(self)
    }
    
    /// Returns the JSON String for the object
    public func toJSONString(prettyPrint: Bool = false) -> String? {
        return Mapper().toJSONString(self, prettyPrint: prettyPrint)
    }
}
//转化为对象数组与json 四个核心方法(字符串json转对象数组,json转对象数组,及对象数组的反转)
public extension Array where Element: BaseMappable {
    
}
//转化为对象集合与json(集合由数组转化而来) 四个核心方法(字符串json转对象集合,json转对象集合,及对象集合的反转)
public extension Set where Element: BaseMappable {
    

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,657评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,631评论 18 399
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,246评论 11 349
  • 锦瑟丝弦响了多年 唯不见一丝断了恋 清水印刻往日眉眼 却无人插上一棵莲。 燕飞过了远方的天 去寻找昔日的真颜 若不...
    徽州球球阅读 394评论 0 2
  • 《战狼2》势如破竹,今天早上累计票房已破34亿! 众所周知,吴京在筹拍《战狼1》时困难重重, 找了几十个明星都不愿...
    戒咖啡cxj阅读 367评论 0 0