【KakaJSON手册】01_JSON转Model_01_基本用法
- 2019 年 10 月 3 日
- 筆記
在iOS开发中,后台返回的数据大多是JSON格式,对应地会被网络框架层解析成Swift中的Dictionary、Array。由于数据类型的复杂、字段的繁多,直接使用Dictionary、Array会比较麻烦,比如items[0]["user"]["name"]这样的使用方式,非常不友善,而且没有智能语法提示。所以很多时候会考虑将JSON转换成Model之后再进行操作,会友善很多,比如items[0].user.name。
- Swift内置了一套Codable机制,可以用于JSON转Model。对于一些简单的模型结构,还是挺好用,但一旦牵扯到复杂的模型结构、一些个性化的需求(比如KeyMapping、类型不匹配时的转换处理、自定义解析规则等),Codable就不太能友善地完成任务了。
- 为了解决上述问题,我编写了一套纯Swift实现的JSON与Model互相转换的框架:KakaJSON,本人非常喜欢龙珠,框架取名自Kaka Rotto(卡卡罗特,孙悟空)
- KakaJSON通过了大量的单元测试用例(目前有80多个测试用例,未来会增加到上百个测试用例,也非常欢迎大家提供各种应用场景和测试用例),应对各种常用的数据场景,对外提供了一些友善易用、扩展性强、可高度个性化定制需求的接口,内置了Metedata缓存等机制,加快转换速度。
- 本教程是为了让大家能够快速上手KakaJSON,挖掘它内部的各种功能,发挥它的最大威力。未来也可能会推出一些源码分析的文章。
- 本文是《KakaJSON手册》系列文章的第一篇
最简单的Model
import KakaJSON // ① 让模型类型遵守`Convertible`协议 struct Cat: Convertible { var name: String = "" var weight: Double = 0.0 } // json也可以是NSDictionary、NSMutableDictionary类型 let json: [String: Any] = [ "name": "Miaomiao", "weight": 6.66 ] // ② 直接调用json的model方法,传入模型类型,返回模型实例 let cat1 = json.kj.model(Cat.self) XCTAssert(cat1.name == "Miaomiao") XCTAssert(cat1.weight == 6.66) // 或者也可以调用一个全局函数来完成JSON转模型 let cat2 = model(from: json, Cat.self)
Type Variable
// 有时类型可能是个变量,比如 var type: Convertible.Type = Cat.self // 调用带有type参数的方法即可 // 由于传入的类型是Convertible.Type变量,因此返回值类型是Convertible,到时根据需求强制转换成自己想要的类型 let cat1 = json.kj.model(type: type) as? Cat // 或者调用全局函数 let cat2 = model(from: json, type: type) as? Cat
Class类型
class Cat: Convertible { var weight: Double = 0.0 var name: String = "" // 由于Swift初始化机制的原因,`Convertible`协议强制要求实现init初始化器 // 这样框架内部才可以完整初始化一个实例 required init() {} } let json = ... let cat = json.kj.model(Cat.self) // 继承自NSObject的类也是一样的用法 class Person: NSObject, Convertible { var name: String = "" var age: Int = 0 // 由于NSObject内部已经有init,因此Person算是重载init,需再加上`override` required override init() {} } let person = json.kj.model(Person.self) struct Dog: Convertible { var weight: Double = 0.0 var name: String = "" // 由于编译器自动帮结构体类型生成了一个init初始化器 // 所以不需要自己再实现init初始化器 } struct Pig: Convertible { var weight: Double var name: String // 如果没有在定义属性的同时指定初始值,编译器是不会为结构体生成init初始化器的 // 所以需要自己实现init初始化器 init() { name = "" weight = 0.0 } }
继承
// 有继承的情况也是照常使用即可 class Person: Convertible { var name: String = "" var age: Int = 0 required init() {} } class Student: Person { var score: Int = 0 var no: String = "" } let json: [String: Any] = [ "name": "jack", "age": 18, "score": 98, "no": "9527" ] let student = json.kj.model(Student.self)
let属性
// KakaJSON也支持let属性 struct Cat: Convertible { // 测试表明:在真机release模式下,对数字类型的let限制比较严格 // 值虽然修改成功了(可以打印Cat结构体发现weight已经改掉了),但get出来还是0.0 // 所以建议使用`private(set) var`取代`let` private(set) var weight: Double = 0.0 let name: String = "" } let json = ... let cat = json.kj.model(Cat.self)
NSNull
struct Cat: Convertible { var weight: Double = 0.0 var name: String = "xx" var data: NSNull? } let json: [String: Any] = [ "name": NSNull(), "weight": 6.6, "data": NSNull() ] let cat = json.kj.model(Cat.self) // 转换失败,保留默认值 XCTAssert(cat.name == "xx") XCTAssert(cat.weight == 6.6) XCTAssert(cat.data == NSNull())
JSONString
// jsonString也可以是NSString、NSMutableString类型 let jsonString = """ { "name": "Miaomiao", "weight": 6.66 } """ // 跟JSON的用法是一样的 let cat1 = jsonString.kj.model(Cat.self) let cat2 = model(from: jsonString, Cat.self) var type: Convertible.Type = Cat.self let cat3 = jsonString.kj.model(type: type) as? Cat let cat4 = model(from: jsonString, type: type) as? Cat
JSONData
// jsonData也可以是NSData、NSMutableData类型 let jsonData = """ { "name": "Miaomiao", "weight": 6.66 } """.data(using: .utf8)! // 跟JSON的用法是一样的 let cat1 = jsonData.kj.model(Cat.self) let cat2 = model(from:jsonData, Cat.self) var type: Convertible.Type = Cat.self let cat3 = jsonData.kj.model(type: type) as? Cat let cat4 = model(from: jsonData, type: type) as? Cat
Model嵌套1
// 让需要进行转换的模型都遵守`Convertible`协议 struct Book: Convertible { var name: String = "" var price: Double = 0.0 } struct Car: Convertible { var name: String = "" var price: Double = 0.0 } struct Dog: Convertible { var name: String = "" var age: Int = 0 } struct Person: Convertible { var name: String = "" var car: Car? var books: [Book]? var dogs: [String: Dog]? } let json: [String: Any] = [ "name": "Jack", "car": ["name": "BMW7", "price": 105.5], "books": [ ["name": "Fast C++", "price": 666.6], ["name": "Data Structure And Algorithm", "price": 1666.6] ], "dogs": [ "dog0": ["name": "Larry", "age": 5], "dog1": ["name": "ErHa", "age": 2] ] ] // 也是如此简单,不用再做额外的操作 let person = json.kj.model(Person.self) XCTAssert(person.car?.name == "BMW7") XCTAssert(person.books?[1].name == "Data Structure And Algorithm") XCTAssert(person.dogs?["dog0"]?.name == "Larry")
Model嵌套2
// Set也能像Array那样支持Model嵌套 // Set要求存放的元素遵守Hashable协议 struct Book: Convertible, Hashable { var name: String = "" var price: Double = 0.0 } struct Person: Convertible { var name: String = "" var books: Set<Book>? } let json: [String: Any] = [ "name": "Jack", "books": [ ["name": "Fast C++", "price": 666.6] ] ] let person = json.kj.model(Person.self) XCTAssert(person.name == "Jack") XCTAssert(person.books?.count == 1) // 从Set中取出来是个Book模型 let book = person.books?.randomElement() XCTAssert(book?.name == "Fast C++") XCTAssert(book?.price == 666.6)
Model嵌套3
struct Car: Convertible { var name: String = "" var price: Double = 0.0 } class Dog: Convertible { var name: String = "" var age: Int = 0 required init() {} init(name: String, age: Int) { self.name = name self.age = age } } struct Person: Convertible { var name: String = "" // 如果你的模型有默认值,KakaJSON内部不会再创建新的模型 // 会直接重复利用你创建的模型,节省内存分配和初始化的开销 var car: Car = Car(name: "Bently", price: 106.5) var dog: Dog = Dog(name: "Larry", age: 5) } let json: [String: Any] = [ "name": "Jake", "car": ["price": 305.6], "dog": ["name": "Wangwang"] ] let person = json.kj.model(Person.self) XCTAssert(person.name == "Jake") // 保留默认值 XCTAssert(person.car.name == "Bently") // 从json解析过来的值 XCTAssert(person.car.price == 305.6) // 从json解析过来的值 XCTAssert(person.dog.name == "Wangwang") // 保留默认值 XCTAssert(person.dog.age == 5)
递归
class Person: Convertible { var name: String = "" var parent: Person? required init() {} } let json: [String: Any] = [ "name": "Jack", "parent": ["name": "Jim"] ] let person = json.kj.model(Person.self) XCTAssert(person.name == "Jack") XCTAssert(person.parent?.name == "Jim")
泛型
struct NetResponse<Element>: Convertible { let data: Element? = nil let msg: String = "" private(set) var code: Int = 0 } struct User: Convertible { let id: String = "" let nickName: String = "" } struct Goods: Convertible { private(set) var price: CGFloat = 0.0 let name: String = "" } let json1 = """ { "data": {"nickName": "KaKa", "id": 213234234}, "msg": "Success", "code" : 200 } """ let response1 = json1.kj.model(NetResponse<User>.self) XCTAssert(response1?.msg == "Success") XCTAssert(response1?.code == 200) XCTAssert(response1?.data?.nickName == "KaKa") XCTAssert(response1?.data?.id == "213234234") let json2 = """ { "data": [ {"price": "6199", "name": "iPhone XR"}, {"price": "8199", "name": "iPhone XS"}, {"price": "9099", "name": "iPhone Max"} ], "msg": "Success", "code" : 200 } """ let response2 = json2.kj.model(NetResponse<[Goods]>.self) XCTAssert(response2?.msg == "Success") XCTAssert(response2?.code == 200) XCTAssert(response2?.data?.count == 3) XCTAssert(response2?.data?[0].price == 6199) XCTAssert(response2?.data?[0].name == "iPhone XR") XCTAssert(response2?.data?[1].price == 8199) XCTAssert(response2?.data?[1].name == "iPhone XS") XCTAssert(response2?.data?[2].price == 9099) XCTAssert(response2?.data?[2].name == "iPhone Max")
Model数组
struct Car: Convertible { var name: String = "" var price: Double = 0.0 } // json数组可以是Array<[String: Any]>、NSArray、NSMutableArray let json: [[String: Any]] = [ ["name": "Benz", "price": 98.6], ["name": "Bently", "price": 305.7], ["name": "Audi", "price": 64.7] ] // 调用json数组的modelArray方法即可 let cars = json.kj.modelArray(Car.self) XCTAssert(cars[1].name == "Bently") // 同样的还有其他方式 let cars2 = modelArray(from: json, Car.self) var type: Convertible.Type = Car.self let cars3 = json.kj.modelArray(type: type) as? [Car] let cars4 = modelArray(from: json, type: type) as? [Car] // 另外,jsonString转为Model数组,也是如此简单 let jsonString = "...." let cars5 = jsonString.kj.modelArray(Car.self) let cars6 = modelArray(from: jsonString, Car.self) let cars7 = jsonString.kj.modelArray(type: type) as? [Car] let cars8 = modelArray(from: jsonString, type: type) as? [Car]
Convert
// 如果你想把JSON数据转换到原本已经创建好的模型实例上,可以使用convert方法 struct Cat: Convertible { var name: String = "" var weight: Double = 0.0 } let json: [String: Any] = [ "name": "Miaomiao", "weight": 6.66 ] var cat = Cat() // .kj_m是.kj的mutable版本,牵扯到修改实例本身都是.kj_m开头 cat.kj_m.convert(json) XCTAssert(cat.name == "Miaomiao" XCTAssert(cat.weight == 6.66)
监听
// 有时候可能想在JSON转模型之前、之后做一些额外的操作 // KakaJSON会在JSON转模型之前调用模型的kj_willConvertToModel方法 // KakaJSON会在JSON转模型之后调用模型的kj_didConvertToModel方法 struct Car: Convertible { var name: String = "" var age: Int = 0 mutating func kj_willConvertToModel(from json: [String: Any]) { print("Car - kj_willConvertToModel") } mutating func kj_didConvertToModel(from json: [String: Any]) { print("Car - kj_didConvertToModel") } } let name = "Benz" let age = 100 let car = ["name": name, "age": age].kj.model(Car.self) // Car - kj_willConvertToModel // Car - kj_didConvertToModel XCTAssert(car.name == name) XCTAssert(car.age == age) /*************************************************************/ // 同样也支持类 class Person: Convertible { var name: String = "" var age: Int = 0 required init() {} func kj_willConvertToModel(from json: [String: Any]) { print("Person - kj_willConvertToModel") } func kj_didConvertToModel(from json: [String: Any]) { print("Person - kj_didConvertToModel") } } class Student: Person { var score: Int = 0 override func kj_willConvertToModel(from json: [String: Any]) { // 如果有必要的话,可以调用super的实现 super.kj_willConvertToModel(from: json) print("Student - kj_willConvertToModel") } override func kj_didConvertToModel(from json: [String: Any]) { // 如果有必要的话,可以调用super的实现 super.kj_didConvertToModel(from: json) print("Student - kj_didConvertToModel") } } let name = "jack" let age = 10 let score = 100 let student = ["name": name, "age": age, "score": score].kj.model(Student.self) // Person - kj_willConvertToModel // Student - kj_willConvertToModel // Person - kj_didConvertToModel // Student - kj_didConvertToModel XCTAssert(student.name == name) XCTAssert(student.age == age) XCTAssert(student.score == score)