【KakaJSON手冊】03_JSON轉Model_03_key處理

  • 2019 年 10 月 3 日
  • 筆記

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

最基本的用法

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. 如果沒有12,使用Student父類的ConvertibleConfig配置   4. 如果沒有123,使用Student父類的父類的ConvertibleConfig配置   5. 如果沒有1234,使用Student父類的父類的父類的....ConvertibleConfig配置   6. 如果沒有12345,使用全局的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")