【KakaJSON手冊】01_JSON轉Model_01_基本用法

在iOS開發中,後臺返回的數據大可能是JSON格式,對應地會被網絡框架層解析成Swift中的Dictionary、Array。因爲數據類型的複雜、字段的繁多,直接使用Dictionary、Array會比較麻煩,好比items[0]["user"]["name"]這樣的使用方式,很是不友善,並且沒有智能語法提示。因此不少時候會考慮將JSON轉換成Model以後再進行操做,會友善不少,好比items[0].user.namehtml

  • 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)
相關文章
相關標籤/搜索