收藏swiftflutter

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

2017-12-25  本文已影响44人  WhoJun

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

上一篇下一篇

猜你喜欢

热点阅读