【KakaJSON手冊】03_JSON轉Model_03_key處理

有時候,服務器返回的JSON數據的key跟客戶端模型的屬性名可能不一致,好比客戶端遵照駝峯規範叫作nickName,而服務器端返回的JSON可能叫作nick_name。這時候爲了保證數據轉換成功,就須要對模型屬性名和JSON的key進行相應的映射。KakaJSON提供了簡單易用的映射方式。git

最基本的用法

struct Person: Convertible {
    var nickName: String = ""
    var mostFavoriteNumber: Int = 0
    var birthday: String = ""
    
    // 實現kj_modelKey方法
    // 會傳入模型的屬性`property`做爲參數,返回值就是屬性對應的key
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // 根據屬性名來返回對應的key
        switch property.name {
            
        // 模型的`nickName`屬性 對應 JSON中的`nick_name`
        case "nickName": return "nick_name"
            
        // 模型的`mostFavoriteNumber `屬性 對應 JSON中的`most_favorite_number `
        case "mostFavoriteNumber": return "most_favorite_number"
            
        // 模型剩下的其餘屬性,直接用屬性名做爲JSON的key(屬性名和key保持一致)
        default: return property.name
            
        }
    }
}

let nick_name = "ErHa"
let most_favorite_number = 666
let birthday = "2011-10-12"

let json: [String: Any] = [
    "nick_name": nick_name,
    "most_favorite_number": most_favorite_number,
    "birthday": birthday
]

let student = json.kj.model(Person.self)
XCTAssert(student.nickName == nick_name)
XCTAssert(student.mostFavoriteNumber == most_favorite_number)
XCTAssert(student.birthday == birthday)

駝峯 -> 下劃線

struct Person: Convertible {
    var nickName: String = ""
    var mostFavoriteNumber: Int = 0
    var birthday: String = ""
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // 因爲開發中可能常常遇到`駝峯`映射`下劃線`的需求,KakaJSON已經內置了處理方法
        // 直接調用字符串的underlineCased方法就能夠從`駝峯`轉爲`下劃線`
        // `nickName` -> `nick_name`
        return property.name.kj.underlineCased()
    }
}

let nick_name = "ErHa"
let most_favorite_number = 666
let birthday = "2011-10-12"

let json: [String: Any] = [
    "nick_name": nick_name,
    "most_favorite_number": most_favorite_number,
    "birthday": birthday
]

let student = json.kj.model(Person.self)
XCTAssert(student.nickName == nick_name)
XCTAssert(student.mostFavoriteNumber == most_favorite_number)
XCTAssert(student.birthday == birthday)

下劃線 -> 駝峯

struct Person: Convertible {
    var nick_name: String = ""
    var most_favorite_number: Int = 0
    var birthday: String = ""
    
    // KakaJSON也給字符串內置了camelCased方法,能夠由`下劃線`轉爲`駝峯`
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // `nick_name` -> `nickName`
        return property.name.kj.camelCased()
    }
}

let nickName = "ErHa"
let mostFavoriteNumber = 666
let birthday = "2011-10-12"

let json: [String: Any] = [
    "nickName": nickName,
    "mostFavoriteNumber": mostFavoriteNumber,
    "birthday": birthday
]

let student = json.kj.model(Person.self)
XCTAssert(student.nick_name == nickName)
XCTAssert(student.most_favorite_number == mostFavoriteNumber)
XCTAssert(student.birthday == birthday)

繼承

// 子類能夠繼承父類的實現

class Person: Convertible {
    var nickName: String = ""
    required init() {}
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // `nickName` -> `nick_ame`
        return property.name.kj.underlineCased()
    }
}

class Student: Person {
    var mathScore: Int = 0
    // Person的kj_modelKey會繼承下來給Student使用
    // `mathScore` -> `math_score`
}

let nick_ame = "Jack"
let math_score = 96
let json: [String: Any] = ["nick_name": nick_ame, "math_score": math_score]

let person = json.kj.model(Person.self)
XCTAssert(person.nickName == nick_ame)

let student = json.kj.model(Student.self)
XCTAssert(student.nickName == nick_ame)
XCTAssert(student.mathScore == math_score)

重寫

// 子類能夠重寫父類的kj_modelKey方法,在父類實現的基礎上加一些本身的需求

class Person: Convertible {
    var name: String = ""
    required init() {}
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // `name` -> `_name_`
        return property.name == "name" ? "_name_" : property.name
    }
}

class Student: Person {
    var score: Int = 0
    
    override func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // 調用了`super.kj_modelKey`,在父類的基礎上加了對`score`的處理
        // `score` -> `_score_`,`name` -> `_name_`
        return property.name == "score" ? "_score_" : super.kj_modelKey(from: property)
    }
}

let name = "Jack"
let score = 96
let json: [String: Any] = ["_name_": name, "_score_": score]

let person = json.kj.model(Person.self)
XCTAssert(person.name == name)

let student = json.kj.model(Student.self)
XCTAssert(student.name == name)
XCTAssert(student.score == score)

重寫 + 覆蓋

// 子類能夠重寫父類的kj_modelKey方法,並徹底覆蓋父類的實現
class Person: Convertible {
    var name: String = ""
    required init() {}
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // `name` -> `_name_`
        return property.name == "name" ? "_name_" : property.name
    }
}

class Student: Person {
    var score: Int = 0
    
    override func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // 這裏並無調用`super. kj_modelKey`
        // 所以`score` -> `_score_`,`name` -> `name`
        return property.name == "score" ? "_score_" : property.name
    }
}

let personName = "Jack"
let person = ["_name_": personName].kj.model(Person.self)
XCTAssert(person.name == personName)

let studentName = "Rose"
let studentScore = 96
let student = ["name": studentName,
               "_score_": studentScore].kj.model(Student.self)
XCTAssert(student.name == studentName)
XCTAssert(student.score == studentScore)

全局配置

// 一旦有須要進行`駝峯` -> `下劃線`的映射,有可能整個項目的全部模型都須要進行映射,畢竟整個項目的命名規範是統一的
// 假設項目中有100多個模型,那麼就須要實現100屢次`kj_modelKey`方法,調用100屢次underlineCased方法
// KakaJSON內置了全局配置機制,能夠1次配置,就能適用於全部的模型(無論是struct仍是class,只要是遵照Convertible協議的模型)

// 全局配置整個項目中只須要配置1次,建議在AppDelegate的didFinishLaunching中配置1次便可
ConvertibleConfig.setModelKey { property in
    property.name.kj.underlineCased()
}
// ConvertibleConfig.setModelKey { $0.name.kj.underlineCased() }

class Person: Convertible {
    var nickName: String = ""
    required init() {}
}

class Student: Person {
    var mathScore: Int = 0
}

struct Car: Convertible {
    var maxSpeed: Double = 0.0
    var name: String = ""
}

let nick_ame = "Jack"
let math_score = 96
let json: [String: Any] = ["nick_name": nick_ame, "math_score": math_score]

let person = json.kj.model(Person.self)
XCTAssert(person.nickName == nick_ame)

let student = json.kj.model(Student.self)
XCTAssert(student.nickName == nick_ame)
XCTAssert(student.mathScore == math_score)

let max_speed = 250.0
let name = "Bently"
let car = ["max_speed": max_speed, "name": name].kj.model(Car.self)
XCTAssert(car.maxSpeed == max_speed)
XCTAssert(car.name == name)

局部配置

// 也能夠給某些特定的類型作配置

// 局部配置
// 因爲Student繼承自Person,因此給Person作的配置,能適用在Student身上
ConvertibleConfig.setModelKey(for: [Person.self, Car.self]) {
    property in
    property.name.kj.underlineCased()
}

class Person: Convertible {
    var nickName: String = ""
    required init() {}
}

class Student: Person {
    var mathScore: Int = 0
}

struct Car: Convertible {
    var maxSpeed: Double = 0.0
    var name: String = ""
}

let nick_ame = "Jack"
let math_score = 96
let json: [String: Any] = ["nick_name": nick_ame, "math_score": math_score]

let person = json.kj.model(Person.self)
XCTAssert(person.nickName == nick_ame)

let student = json.kj.model(Student.self)
XCTAssert(student.nickName == nick_ame)
XCTAssert(student.mathScore == math_score)

let max_speed = 250.0
let name = "Bently"
let car = ["max_speed": max_speed, "name": name].kj.model(Car.self)
XCTAssert(car.maxSpeed == max_speed)
XCTAssert(car.name == name)

配置示例1

// 全局配置
ConvertibleConfig.setModelKey { property in
    property.name.kj.underlineCased()
}

// Person配置
ConvertibleConfig.setModelKey(for: Person.self) { property in
    // `name` -> `_name_`
    property.name == "name" ? "_name_" : property.name
}

// Student配置
ConvertibleConfig.setModelKey(for: Student.self) { property in
    // `score` -> `_score_`,`name` -> `name`
    property.name == "score" ? "_score_" : property.name
}

class Person: Convertible {
    var name: String = ""
    required init() {}
}

class Student: Person {
    var score: Int = 0
}

struct Car: Convertible {
    var maxSpeed: Double = 0.0
    var name: String = ""
}

let personName = "Jack"
let person = ["_name_": personName].kj.model(Person.self)
XCTAssert(person.name == personName)

let studentName = "Rose"
let studentScore = 96
let student = ["name": studentName,
               "_score_": studentScore].kj.model(Student.self)
XCTAssert(student.name == studentName)
XCTAssert(student.score == studentScore)

let max_speed = 250.0
let name = "Bently"
let car = ["max_speed": max_speed, "name": name].kj.model(Car.self)
XCTAssert(car.maxSpeed == max_speed)
XCTAssert(car.name == name)

配置示例2

// 全局配置
ConvertibleConfig.setModelKey { property in
    property.name.kj.underlineCased()
}

// Person配置
ConvertibleConfig.setModelKey(for: Person.self) { property in
    // `name` -> `_name_`
    property.name == "name" ? "_name_" : property.name
}

// Student配置
ConvertibleConfig.setModelKey(for: Student.self) { property in
    // `score` -> `_score_`,`name` -> `name`
    property.name == "score" ? "_score_" : property.name
}

class Person: Convertible {
    var name: String = ""
    required init() {}
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // 能夠經過ConvertibleConfig獲取Person當初的配置
        // `name` -> `_name_`
        return ConvertibleConfig.modelKey(from: property, Person.self)
    }
}

class Student: Person {
    var score: Int = 0
    
    override func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // `score` -> `score`,`name` -> `name`
        return property.name
    }
}

struct Car: Convertible {
    var maxSpeed: Double = 0.0
    var name: String = ""
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        // 能夠經過ConvertibleConfig獲取全局配置
        // `maxSpeed` -> `max_speed`
        // `name` -> `name`
        return ConvertibleConfig.modelKey(from: property)
    }
}

/*
 當配置了多處modelKey時,它們的優先級從高到低,以下所示(以Student類型爲例)
 1. 使用Student的kj_modelKey的實現
 2. 若是沒有1,使用Student的ConvertibleConfig配置
 3. 若是沒有1\2,使用Student父類的ConvertibleConfig配置
 4. 若是沒有1\2\3,使用Student父類的父類的ConvertibleConfig配置
 5. 若是沒有1\2\3\4,使用Student父類的父類的父類的....ConvertibleConfig配置
 6. 若是沒有1\2\3\4\5,使用全局的ConvertibleConfig配置
 */

// Person、Student、Car都實現了kj_modelKey,所以使用kj_modelKey的實現

let personName = "Jack"
let person = ["_name_": personName].kj.model(Person.self)
XCTAssert(person.name == personName)

let studentName = "Rose"
let studentScore = 96
let student = ["name": studentName,
               "score": studentScore].kj.model(Student.self)
XCTAssert(student.name == studentName)
XCTAssert(student.score == studentScore)

let max_speed = 250.0
let name = "Bently"
let car = ["max_speed": max_speed, "name": name].kj.model(Car.self)
XCTAssert(car.maxSpeed == max_speed)
XCTAssert(car.name == name)

複雜的key映射

struct Toy: Convertible {
    var price: Double = 0.0
    var name: String = ""
}

struct Dog: Convertible {
    var name: String = ""
    var age: Int = 0
    var nickName: String?
    var toy: Toy?
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        switch property.name {
        // 對應dog["toy"]
        case "toy": return "dog.toy"
        // 對應data[1]["dog"]["name"]
        case "name": return "data.1.dog.name"
        // 會按順序映射數組中的每個key,直到成功爲止
        // 先映射`nickName`,若是失敗再映射`nick_name`
        // 若是失敗再映射`dog["nickName"]`,若是失敗再映射`dog["nick_name"]`
        case "nickName": return ["nickName", "nick_name", "dog.nickName", "dog.nick_name"]
        default: return property.name
        }
    }
}

let name = "Larry"
let age = 5
let nickName1 = "Jake1"
let nickName2 = "Jake2"
let nickName3 = "Jake3"
let nickName4 = "Jake4"
let toy = (name: "Bobbi", price: 20.5)

let json: [String: Any] = [
    "data": [10, ["dog" : ["name": name]]],
    "age": age,
    "nickName": nickName1,
    "nick_name": nickName2,
    "dog": [
        "nickName": nickName3,
        "nick_name": nickName4,
        "toy": ["name": toy.name, "price": toy.price]
    ]
]

let dog = json.kj.model(Dog.self)
XCTAssert(dog.name == name)
XCTAssert(dog.age == age)
XCTAssert(dog.nickName == nickName1)
XCTAssert(dog.toy?.name == toy.name)
XCTAssert(dog.toy?.price == toy.price)

/*-------------------------------------------------*/

struct Team: Convertible {
    var name: String?
    var captainName: String?
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        switch property.name {
        case "captainName":     return "captain.name"
        default:                return property.name
        }
    }
}

let teamName = "V"
let captainName = "Quentin"

let json: [String: Any] = [
    "name": teamName,
    "captain.name": captainName,
]
let team = json.kj.model(Team.self)
XCTAssert(team.name == teamName)
XCTAssert(team.captainName == captainName)

/*-------------------------------------------------*/

struct Model: Convertible {
    var valueA: String?
    var valueB: String?
    
    func kj_modelKey(from property: Property) -> ModelPropertyKey {
        switch property.name {
        case "valueA":          return "a.0.a"
        case "valueB":          return "b.0.b.0.b"
        default:                return property.name
        }
    }
}

let json: [String: Any] = [
    "a": [ "l", "u", "o" ],
    "b": [
        [ "b": [ "x", "i", "u" ] ]
    ]
]
let model = json.kj.model(Model.self)
XCTAssert(model.valueA == "l")
XCTAssert(model.valueB == "x")
相關文章
相關標籤/搜索