当前位置: > > > Swift - 使用反射将自定义对象数据序列化成JSON数据

Swift - 使用反射将自定义对象数据序列化成JSON数据

(本文代码已升级至Swift4)
注意:由于Swift4新增了一个Codable协议可以很方便地实现对象序列化,现在将对象转成JSON数据不用这么麻烦了。具体做法见文章最下方部分。

我们知道苹果从IOS5.0后推出了SDK自带的JSON解决方案JSONSerialization,这是一个非常好用的JSON生成和解析工具,效率也比其他第三方开源项目高。
但用其生成JSON数据有个限制:只能将Foundation对象转换成JSON。即顶层对象必须是Array或者Dictionary,所有的对象必须是StringNumberArrayDictionaryNil的实例。
所以,如果我们想要把自定义类型的数据对象转成JSON数据,JSONSerialization就无能为力了。  

1,将自定义对象转成JSON数据的实现原理(不借助第三方库)
(1)首先我们使用反射(Reflection)对自定义类型的数据对象中所有的属性进行递归遍历,生成字典类型的数据并返回。
(2)接着使用JSONSerialization就可以把这个字典类型的数据转换成jSON数据了。

2,比如我们自定义一个的联系人类
//用户类
class User {
    var name:String = ""  //姓名
    var nickname:String?  //昵称
    var age:Int?   //年龄
    var emails:[String]?  //邮件地址
    var tels:[Telephone]? //电话
    var createTime:Date = Date() //创建时间
}

//电话结构体
struct Telephone {
    var title:String  //电话标题
    var number:String  //电话号码
}

3,JSON串生成的功能实现
(1)首先定义一个叫JSON的协议,并对其扩展,实现协议中定义的两个方法。
toJSONModel():将数据转成可用的JSON模型。
toJSONString():将数据转成JSON字符串(其内部会调用前面的toJSONModel()方法)。
(2)将可选类型(Optional),自定义类(UserTeleTelephone),以及基本数据类型进行扩展,使其遵循JSON协议。
同时对于可选类型还要重写toJSONModel()方法,为的是当可选类型值不存在时返回nil,存在时将其转成具体类型并序列化。
//自定义一个JSON协议
protocol JSON {
    func toJSONModel() -> AnyObject?
    func toJSONString() -> String?
}

//扩展协议方法
extension JSON {
    //将数据转成可用的JSON模型
    func toJSONModel() -> AnyObject? {
        let mirror = Mirror(reflecting: self)
        if mirror.children.count > 0  {
            var result: [String:AnyObject] = [:]
            for case let (label?, value) in mirror.children {
                //print("属性:\(label)     值:\(value)")
                if let jsonValue = value as? JSON {
                    result[label] = jsonValue.toJSONModel()
                }
            }
            
            return result as AnyObject
        }
        return self as AnyObject
    }
    
    //将数据转成JSON字符串
    func toJSONString() -> String? {
        let jsonModel = self.toJSONModel()
        //利用OC的json库转换成Data,
        let data = try? JSONSerialization.data(withJSONObject: jsonModel!,
                                                                         options: [])
        //Data转换成String打印输出
        let str = String(data: data!, encoding: .utf8)
        return str
    }
}

//扩展可选类型,使其遵循JSON协议
extension Optional: JSON {
    //可选类型重写toJSONModel()方法
    func toJSONModel() -> AnyObject? {
        if let x = self {
            if let value = x as? JSON {
                return value.toJSONModel()
            }
        }
        return nil
    }
}

//扩展两个自定义类型,使其遵循JSON协议
extension User: JSON { }
extension Telephone: JSON { }

//扩展Swift的基本数据类型,使其遵循JSON协议
extension String: JSON { }
extension Int: JSON { }
extension Bool: JSON { }
extension Dictionary: JSON { }

//扩展Array,格式化输出
extension Array: JSON {
    //将数据转成可用的JSON模型
    func toJSONModel() -> AnyObject? {
        var result: [AnyObject] = []
        for value in self {
            if let jsonValue = value as? JSON , let jsonModel = jsonValue.toJSONModel(){
                result.append(jsonModel)
            }
        }
        return result as AnyObject
    }
}

//扩展Date日期类型,格式化输出
extension Date: JSON {
    //将数据转成可用的JSON模型
    func toJSONModel() -> AnyObject? {
        // 创建一个日期格式器
        let dformatter = DateFormatter()
        // 为日期格式器设置格式字符串
        dformatter.dateFormat = "yyyy年MM月dd日 HH:mm:ss"
        // 使用日期格式器格式化日期、时间
        let datestr = dformatter.string(from: self)
        return datestr as AnyObject
    }
}

4,测试样例
//创建一个User实例对象
let user1 = User()
user1.name = "hangge"
user1.age = 100
user1.emails = ["hangge@hangge.com","system@hangge.com"]
//添加动画
let tel1 = Telephone(title: "手机", number: "123456")
let tel2 = Telephone(title: "公司座机", number: "001-0358")
user1.tels = [tel1, tel2]

//输出json字符串
print(user1.toJSONString()!)
看到控制台输出如下信息
格式化后数据如下:
{
    "tels": [
        {
            "number": "123456",
            "title": "手机"
        },
        {
            "number": "001-0358",
            "title": "公司座机"
        }
    ],
    "createTime": "2016年09月01日 16:37:53",
    "age": 100,
    "name": "hangge",
    "emails": [
        "hangge@hangge.com",
        "system@hangge.com"
    ]
}

附:通过 Codable 协议实现对象转 JSON 字符串

这里我们同样定义一个叫 JSON 的协议,只不过它继承了 Codable 协议。可以看到代码比上面的简单很多。
//自定义一个JSON协议
protocol JSON: Codable {
    func toJSONString() -> String?
}

//扩展协议方法
extension JSON {
    //将数据转成可用的JSON模型
    func toJSONString() -> String? {
        //encoded对象
        if let encodedData = try? JSONEncoder().encode(self) {
            //从encoded对象获取String
            return String(data: encodedData, encoding: .utf8)
        }
        return nil
    }
}

//用户类
class User:JSON {
    var name:String = ""  //姓名
    var nickname:String?  //昵称
    var age:Int?   //年龄
    var emails:[String]?  //邮件地址
    var tels:[Telephone]? //电话
    var createTime:Date = Date() //创建时间
}

//电话结构体
struct Telephone:JSON {
    var title:String  //电话标题
    var number:String  //电话号码
}

使用方法也和上面一样:
//创建一个User实例对象
let user1 = User()
user1.name = "hangge"
user1.age = 100
user1.emails = ["hangge@hangge.com","system@hangge.com"]
//添加动画
let tel1 = Telephone(title: "手机", number: "123456")
let tel2 = Telephone(title: "公司座机", number: "001-0358")
user1.tels = [tel1, tel2]

//输出json字符串
print(user1.toJSONString()!)
评论4
  • 4楼
    2016-08-01 15:13
    CodeSeeder

    如果含有自定义数组数据,如何取消数组下标呢?

    站长回复

    修改Array的扩展就可以了,见我文章末尾最新添加的内容。

  • 3楼
    2016-01-26 14:55
    Gemini

    请教航哥, 我按照上述方法,为什么传递的参数,顺序会变化,并且数据会丢失?
    let register = EnrollUser3(Phone: enroll.Phone, ACode: enroll.ACode, Psw: enroll.Psw, Loc: loc(Position: position(Lng: enroll.Lng, Lat: enroll.Lat), Time: enroll.Time!!, Addr: enroll.Add))
    print(register)
    let enrollInf = register.toJSONString()!
    print(enrollInf)
    控制台输出信息如下
    EnrollUser3(Phone: "15021475725", ACode: "2557", Psw: "4C0FC85FA1F01624C8A4C4D938234D2D", Loc: HuoZhanApp.loc(Position: HuoZhanApp.position(Lng: 116.403, Lat: 39.913), Time: 2016-01-26 03:20:59 +0000, Addr: "北京市东城区广场西侧路"))
    {"Phone":"15021475725","Loc":{"Position":{},"Addr":"北京市东城区广场西侧路"},"ACode":"2557","Psw":"4C0FC85FA1F01624C8A4C4D938234D2D"}

    ======
    可以看到Loc顺序变到前边了,而且Position的数据和Time的数据都丢失了!

    站长回复

    顺序变化是正常的,因为字典本来就是无序的。数据传输过去后,各个value也是通过key来取,而不是用顺序取,所以没什么影响。
    数据不显示和丢失问题,提供的信息太少无法判断。最好写一个完整的简单样例(要能复现你这个问题,包括类定义什么的,正常的多余属性可以去掉),粘贴到评论框我看看。

  • 2楼
    2016-01-18 13:33
    小华

    写的很好哦,最近在学习swift,会一直关注

    站长回复

    谢谢你的支持

  • 1楼
    2016-01-13 11:58
    Gemini

    十分感谢航哥的辛勤,受益颇多!谢谢您!

    站长回复

    不客气,我会持续更新的。