Swift-实现字典模型转换(一)

最近太忙太久没写文章了,感觉有点不会写了。
好了,废话不多说开始swift模型转字典,字典转模型的小小工具类编写和思路。

思路:

以前 OC 是使用 runtime 获取到 Model 里面的 key - value 然后用KVC进行赋值。
那么Swift 也可以使用 Mirror 来获取 Model 里面的 key - value 哦~,~。

有了思路我现在就开始编写,我在项目遇到的坑还有编写遇到的坑,还有些没解决的坑。😄

以前 OC 中写Model会继承 NSObject ,按我的习惯来,在Swift我也习惯用NSObject来写Model,所以现在我就用 NSObject来写扩展(extension)。

第一步,使用Mirror动态获取Modelkey
第二步,创建Model,使用KVC赋值。
第三步,然后再转Dictionary
基本步骤就这样子把。

模型:

class Person : NSObject {
  var name:String = ""
  var age:Int = 0
  var desc:String?
  var height:Double?
}

把大部分可能性都写了下,好测试。
有些坑都在?上,也就是Optional(可选值)🤦‍♂️。

扩展:

第一步:

先来点简单的,使用Mirror打印Model的 keytype

extension NSObject {
    func variables() {
        let mirr = Mirror(reflecting: self)
        //Mirror 的 children 是一个 (label: String?, value: Any) 元组类型,表示该类的所有属性的名字和类型
        for case let (label,value) in mirr.children {
            if let key = label {
                let valueMirr = Mirror(reflecting: value)
                //subjectType 类型
                debugPrint("key -- \(key)  type -- \(valueMirr.subjectType)")
            }
        }
    }
}

终端打印:

Test Case '-[ObjectConversionTests.ObjectConversionTests testModel]' started.
"key -- name  type -- String"
"key -- age  type -- Int"
"key -- desc  type -- Optional<String>"
"key -- height  type -- Optional<Double>"

然后就能看到写 key type,这样就好办多了。知道key就可以进行KVC了。

为了以后更可读一点,把一些常用基本类型进行简单的封装成枚举。

enum VariableType {
    case number  //数字
    case string  //字符串
    case bool  //布尔
    case array(String)  //数组 String 是对应对象
    case dictionary  //字典
    case object  //对象
    case null  //NSNull
    case unknown  //无法解析数据
}

这一招我是模仿SwfitlyJson在做的,可以在Github搜索到😄。

我现在就将数据进行分类组合~~

typealias VarData = (String,VariableType,String,Any.Type)// key type modelStr class  数据结构

extension NSObject {
    
    func variables() {
        let mirr = Mirror(reflecting: self)
        //Mirror 的 children 是一个 (label: String?, value: Any) 元组类型,表示该类的所有属性的名字和类型
        var keys = [VarData]()
        keys.append(contentsOf:self.categroy(mirr))
        debugPrint(keys)
    }
    
    func categroy(_ mirr: Mirror) -> [VarData] {
        var keys = [VarData]()
        for case let (label,value) in mirr.children {
            if let key = label {
                let valueMirr = Mirror(reflecting: value)
                var type = VariableType.unknown
                var str = ""
                if valueMirr.subjectType == String?.self || valueMirr.subjectType == String.self {
                    type = .string
                } else if valueMirr.subjectType == Int?.self ||
                    valueMirr.subjectType == Int.self ||
                    valueMirr.subjectType == Int64?.self ||
                    valueMirr.subjectType == Int64.self ||
                    valueMirr.subjectType == Float?.self ||
                    valueMirr.subjectType == Float.self ||
                    valueMirr.subjectType == Double?.self ||
                    valueMirr.subjectType == Double.self {
                    type = .number
                } else if valueMirr.subjectType == Bool?.self ||
                    valueMirr.subjectType == Bool.self {
                    type = .bool
                } else {
                    let typestr = "\(valueMirr.subjectType)"
                    if typestr.contains("Array") {
                        str = typestr
                        str = str.replacingOccurrences(of: "Optional<", with: "")
                        str = str.replacingOccurrences(of: "Array<", with: "")
                        str = str.replacingOccurrences(of: ">", with: "")
                        type = .array(str)
                    } else if typestr.contains("Dictionary") {
                        type = .dictionary
                    } else {
                        if valueMirr.subjectType is NSObject.Type {
                            type = .object
                        } else if valueMirr.subjectType is NSObject?.Type {
                            type = .object
                        }
                    }
                }
                keys.append((key,type,str,valueMirr.subjectType))
            }
        }
        return keys
    }
}
Test Case '-[ObjectConversionTests.ObjectConversionTests testModel]' started.
[("name", ObjectConversion.VariableType.string, "", Swift.String),
 ("age", ObjectConversion.VariableType.number, "", Swift.Int),
 ("desc", ObjectConversion.VariableType.string, "", Swift.Optional<Swift.String>),
 ("height", ObjectConversion.VariableType.number, "", Swift.Optional<Swift.Double>)]

数据改造后就能清晰的看清楚数据对应的类型是啥了,是不是瞬间感觉友善很多了哈。

你们应该会有点疑问为啥那么多基础类型都做双判断,这就是坑点。

if valueMirr.subjectType == Double.self {
...
}

起初我的判断是这样的以上判断。

但是他识别不了Optional的变量,所以我基本数据类型都会判断Optional

if valueMirr.subjectType == Double.self
|| valueMirr.subjectType == Double?.self {
...
}

所以我才改成上面这样子=,=。

本来我是想过用字符串来进行判断的,后来想到这只是基本数据类型,转来转去太麻烦了。

话题转回去,接着继续讲转换成模型的问题。

现在数据已经有我需要的东东了。(key type)

现在做创建对象和赋值的操作。

extension NSObject {
...
    class func createObj(dict:Dictionary<String,Any>) -> Self {
        let obj = self.init()//创建对象
        //赋值操作
        return obj
    }
}

那么赋值操作步骤:
获取key type关联关系,上面我们就已经获取到了。
那么获取后使用KVC进行赋值。

extension NSObject {
...
   func variables() -> [VarData] {
        let mirr = Mirror(reflecting: self)
        //Mirror 的 children 是一个 (label: String?, value: Any) 元组类型,表示该类的所有属性的名字和类型
        var keys = [VarData]()
        keys.append(contentsOf:self.categroy(mirr))
        debugPrint(keys)
        return keys
    }
...
    /**根据我的项目需求 服务器key 和模型key 大小写不等。。。
     * 此方法就过滤大小写不等的问题。
     */
    private func ignoreCase(dict:Dictionary<String,Any>,key:String) -> Any? {
        if let value = dict[key] {
            return value
        } else if let value = dict[key.uppercased()] {
            return value
        } else if let value = dict[key.lowercased()] {
            return value
        } else {
            let dks = dict.keys.filter({ (key2) -> Bool in
                if key.uppercased() == key2.uppercased() {
                    return true
                } else if key.lowercased() == key2.lowercased() {
                    return true
                }
                return false
            })
            if dks.count == 1 {
                if let value = dict[dks.first!] {
                    return value
                }
            }
        }
        return nil
    }

    func pm_setValuesForKeys(_ dict:[String:Any]) {
        let vars = self.variables()
        for v in vars {
            switch v.1 {
            case .null:
                break
            case.unknown:
                break
            case .number:
                if let value = ignoreCase(dict:dict,key:v.0) as? String {
                    let decimal = NSDecimalNumber(string: value)
                    if decimal != NSDecimalNumber.notANumber {
                        self.setValue(decimal, forKey: v.0)
                    }
                } else {
                    if let value = ignoreCase(dict:dict,key:v.0) {
                        if !(value is NSNull) {
                            self.setValue(value, forKey: v.0)
                        }
                    }
                }
            case .string:
                if let value = ignoreCase(dict:dict,key:v.0) as? NSNumber {
                    self.setValue(value.stringValue, forKey: v.0)
                } else {
                    if let value = ignoreCase(dict:dict,key:v.0) as? String {
                        self.setValue(value, forKey: v.0)
                    } else {
                        if let value = ignoreCase(dict:dict,key:v.0) {
                            if !(value is NSNull) {
                                self.setValue(String(describing: ignoreCase(dict:dict,key:v.0)), forKey: v.0)
                            }
                        }
                    }
                }
                break
            default:
                if let value = ignoreCase(dict:dict,key:v.0) {
                    if !(value is NSNull) {
                        self.setValue(value, forKey: v.0)
                    }
                }
            }
        }
    }
   class func createObj(dict:Dictionary<String,Any>) -> Self {
        let obj = self.init() //创建对象
        obj.pm_setValuesForKeys(dict) //赋值操作
        return obj
    }
}

那么我们字典转换模型大概就这样完成了。

但是呢~~运行后会奔溃,这是为什么呢。

[ObjectConversionTests.ObjectConversionTests testModel] : failed: caught "NSUnknownKeyException", "[<ObjectConversion.Person 0x6000000a1920> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key height."

在Swift4.0这句话说找不到对应key。但是我利用Mirror获取到key了呀。。What?
然后我发现Swift3.0 和 Swift4.0 模型的变量定义是不一样的。
Swift4.0是需要加@objc前缀修饰。

class Person : NSObject {
  @objc var name:String = ""
  @objc var age:Int = 0
  @objc var desc:String?
  @objc var height:Double = 0
}

然后在重新运行,就运行成功了。
在Swift3.0是不需要加的哦,具体为啥这里就不解释了=,=。

做到这一步我们可以简单的赋值了,但是还有Model里面变量Model的赋值还没做。

下集分晓。。。刚好有活干了 T_T。

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

推荐阅读更多精彩内容