美文网首页收藏swiftflutter
Swift-实现字典模型转换(一)

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

作者: WhoJun | 来源:发表于2017-12-25 12:07 被阅读44次

    最近太忙太久没写文章了,感觉有点不会写了。
    好了,废话不多说开始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。

    相关文章

      网友评论

        本文标题:Swift-实现字典模型转换(一)

        本文链接:https://www.haomeiwen.com/subject/xqyrwxtx.html