HandyJSON浅析(二)

咱们在HandyJSON浅析(一)中讲了HandyJSON是怎么从类信息中获取这个类中的属性个数、以及属性信息,比如属性在实例中的偏移量,类型信息等,有了这些信息之后,剩下的就是如何将服务端返回的信息,写到这个实例中去
  1. 获取到的属性信息最后都存放在properties里(此段代码可以在HandyJSON源码里搜到)
guard let properties = getProperties(forType: Self.self) else {
   InternalLogger.logDebug("Failed when try to get properties from type: \(type(of: Self.self))")
   return
 }
  1. properties存储的信息就是所有属性的信息,如下:包含属性名称、属性类型、属性的偏移量(key就是属性的name)


    image.png
  2. 从服务端返回的json中解析出每个属性对应的值

  • 先从json中拿到属性对应的值
if let rawValue = getRawValueFrom(dict: _dict, property: propertyDetail, mapper: mapper)
  • 咱们再看一下这个函数的实现(如下),实际上大部分场景只会走最后一行,函数前面是进行一些特殊处理的逻辑,比如服务端返回的是“id”但是我本地写的是“carId”,咱们先讲解一些正常流程的,特殊处理的后面会再单独说
fileprivate func getRawValueFrom(dict: [String: Any], property: PropertyInfo, mapper: HelpingMapper) -> Any? {
    let address = Int(bitPattern: property.address)
    if let mappingHandler = mapper.getMappingHandler(key: address) {
        if let mappingPaths = mappingHandler.mappingPaths, mappingPaths.count > 0 {
            for mappingPath in mappingPaths {
                if let _value = dict.findValueBy(path: mappingPath) {
                    return _value
                }
            }
            return nil
        }
    }
    if HandyJSONConfiguration.deserializeOptions.contains(.caseInsensitive) {
        return dict[property.key.lowercased()]
    }
    //大部分场景只会走这一行,很简单就是根据key从json里取出对应的数据
    return dict[property.key]
}
  • 对取出的数据后,再看看符不符合要求,比如类型是否对应的上,如果对应不上,再进行简单的类型转换处理
 if let convertedValue = convertValue(rawValue: rawValue, property: propertyDetail, mapper: mapper)

函数实现如下

fileprivate func convertValue(rawValue: Any, property: PropertyInfo, mapper: HelpingMapper) -> Any? {
   //不对数据进行特殊处理的话,这是不会执行的
    if rawValue is NSNull { return nil }
    if let mappingHandler = mapper.getMappingHandler(key: Int(bitPattern: property.address)), let transformer = mappingHandler.assignmentClosure {
        return transformer(rawValue)
    }
   
   //大部分情况会走这
    if let transformableType = property.type as? _Transformable.Type {//如果已经兼容这种属性类型的处理。还需要注意一点的就是,如果写成可选型,会多一层转换,先将可选类型解包成对应类型,再进行转换,所以如果可以的话,尽量少的用可选类型
        return transformableType.transform(from: rawValue)//如果需要的话,会进行简单的类型处理,比如我定义的是bool,结果服务端返回了个字符串"0",那就会自动处理为false在第一篇文章里讲了。
    } else {//如果这个库没兼容的类型,则直接获取
        return extensions(of: property.type).takeValue(from: rawValue)
    }
}
//举个例子:自己定义的类型和服务端返回的不一致时的处理,还有别的类型处理,可以去库里看
extension Bool: _BuiltInBasicType {

    static func _transform(from object: Any) -> Bool? {
        switch object {
        case let str as NSString:
            let lowerCase = str.lowercased
            if ["0", "false"].contains(lowerCase) {
                return false
            }
            if ["1", "true"].contains(lowerCase) {
                return true
            }
            return nil
        case let num as NSNumber:
            return num.boolValue
        default:
            return nil
        }
    }

    func _plainValue() -> Any? {
        return self
    }
}
  1. 想要的数据拿到以后,就开始赋值了,函数如下
assignProperty(convertedValue: convertedValue, instance: instance, property: propertyDetail)

函数实现如下

fileprivate func assignProperty(convertedValue: Any, instance: _ExtendCustomModelType, property: PropertyInfo) {
    if property.bridged {
        //如果是继承自NsObject的类型,也就是OC类型,那可以直接用KVC进行赋值
        (instance as! NSObject).setValue(convertedValue, forKey: property.key)
    } else {
        //如果是Swift类型,那就往实例中对应属性的位置里写上服务端返回的值
        extensions(of: property.type).write(convertedValue, to: property.address)
    }
}



//extensions(of: property.type).write这个方法点进去就会发现,实际上最重要的是这个方法
public static func write(_ value: Any, to storage: UnsafeMutableRawPointer) {
        guard let this = value as? Self else {
            return
        }
        storage.assumingMemoryBound(to: self).pointee = this
    }
  1. 特殊情况的处理
    一些特殊需求的处理,官方也给出了一些Demo。
    地址是https://github.com/alibaba/HandyJSON/blob/master/README_cn.md,咱们挑几个说一下
  • 指定解析路径
HandyJSON支持指定从哪个具体路径开始解析,反序列化到Model。

class Cat: HandyJSON {
    var id: Int64!
    var name: String!

    required init() {}
}

let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}"

if let cat = Cat.deserialize(from: jsonString, designatedPath: "data.cat") {
    print(cat.name)
}

实现原理
public static func deserializeFrom(dict: [String: Any]?, designatedPath: String? = nil) -> T? {
     var targetDict = dict
     if let path = designatedPath {//如果指定了解析节点,则获取指定节点后的数据
          targetDict = getInnerObject(inside: targetDict, by: path) as? [String: Any]
      }
     if let _dict = targetDict {//如果没指定解析路径,则直接解析返回的所有数据
        return T._transform(dict: _dict) as? T
     }
     return nil
    }

//获取某个节点后的数据的实现如下,也就是从总的json里,根据指定的节点,一层一层的往下获取
fileprivate func getInnerObject(inside object: Any?, by designatedPath: String?) -> Any? {
    var result: Any? = object
    var abort = false
    if let paths = designatedPath?.components(separatedBy: "."), paths.count > 0 {
        var next = object as? [String: Any]
        paths.forEach({ (seg) in
            if seg.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "" || abort {
                return
            }
            if let _next = next?[seg] {
                result = _next
                next = _next as? [String: Any]
            } else {
                abort = true
            }
        })
    }
    return abort ? nil : result
}
  • 自定义解析规则,这是官方Demo里的例子
class Cat: HandyJSON {
    var id: Int64!
    var name: String!
    var parent: (String, String)?

    required init() {}

    func mapping(mapper: HelpingMapper) {
        // specify 'cat_id' field in json map to 'id' property in object
        mapper <<<
            self.id <-- "cat_id"

        // specify 'parent' field in json parse as following to 'parent' property in object
        mapper <<<
            self.parent <-- TransformOf<(String, String), String>(fromJSON: { (rawString) -> (String, String)? in
                if let parentNames = rawString?.characters.split(separator: "/").map(String.init) {
                    return (parentNames[0], parentNames[1])
                }
                return nil
            }, toJSON: { (tuple) -> String? in
                if let _tuple = tuple {
                    return "\(_tuple.0)/\(_tuple.1)"
                }
                return nil
            })

        // specify 'friend.name' path field in json map to 'friendName' property
        mapper <<<
            self.friendName <-- "friend.name"
    }
}

let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\",\"friend\":{\"id\":54321,\"name\":\"Lily\"}}"

if let cat = Cat.deserialize(from: jsonString) {
    print(cat.id)
    print(cat.parent)
    print(cat.friendName)
}

咱们说一下比较常见的,本地变量的名称和服务端返回名称不一致转换的实现,在以前编写OC时经常遇到,咱们变量名不能写成id,但是服务端返回的是id,就需要转换了(但是官方给的demo里,是本地名称是id,服务端返回的是cat_id,别混了)
<<< 和 <--是自定义的运算符,<--的优先级更高

 mapper <<< self.id <-- "cat_id"

先说一下<--

infix operator <-- : LogicalConjunctionPrecedence

public func <-- <T>(property: inout T, name: String) -> CustomMappingKeyValueTuple {
    return property <-- [name]
}

//运算符<--会返回一个元组,简单点理解就是,元组里第一个数据是属性id的地址,元组第二个数据是服务端返回对应数据的真实key,以demo中例子说就类似(0x39393994949, cat_id)
public func <-- <T>(property: inout T, names: [String]) -> CustomMappingKeyValueTuple {
    let pointer = withUnsafePointer(to: &property, { return $0 })
    let key = Int(bitPattern: pointer)
    return (key, MappingPropertyHandler(rawPaths: names, assignmentClosure: nil, takeValueClosure: nil))
}

再说一下 <<<

infix operator <<< : AssignmentPrecedence

//<<<执行这个最终就是将上面说的元组的内容添加到了self.mappingHandlers,实际执行的是self.mappingHandlers[key] = mappingInfo(key是元组的第一个元素,mappingInfo是元组的第二个元素)

public func <<< (mapper: HelpingMapper, mapping: CustomMappingKeyValueTuple) {
    mapper.addCustomMapping(key: mapping.0, mappingInfo: mapping.1)
}

最后说一下是怎么替换的,咱们在最开始的时候说过下面这个函数,就是获取对应字段的数据,说的是正常情况下,只会走最后一行,也就是直接根据属性的name找到value,但是需要name转换的时候,就会走前面的代码了,会根据服务端返回的name,找到对应的value(因为属性的name和服务端返回的不一致)

fileprivate func getRawValueFrom(dict: [String: Any], property: PropertyInfo, mapper: HelpingMapper) -> Any? {
    let address = Int(bitPattern: property.address)
    //因为咱们之前执行了self.mappingHandlers[key] = mappingInfo,所以执行到这就会有值了,说白了就是,服务端返回这个字段的实际key是啥,然后根据findValueBy这个方法,用服务端返回的实际属性名为key,找到对应数据
    if let mappingHandler = mapper.getMappingHandler(key: address) {
        if let mappingPaths = mappingHandler.mappingPaths, mappingPaths.count > 0 {
            for mappingPath in mappingPaths {
                if let _value = dict.findValueBy(path: mappingPath) {
                    return _value
                }
            }
            return nil
        }
    }
    if HandyJSONConfiguration.deserializeOptions.contains(.caseInsensitive) {
        return dict[property.key.lowercased()]
    }
    return dict[property.key]
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容