摘要
本文描述了一種適用于Swift語言的JSON與MODEL互轉(zhuǎn)方法见咒,
這里不涉及復(fù)雜Decode機制(Codable)和不安全的內(nèi)存訪問(HandyJSON)现使。
僅使用Swift的KVC和Macro達到目的荐绝。
從KVC開始
假設(shè)存在一個struct類型Person和對應(yīng)結(jié)構(gòu)的JSON:
struct Person {
var name = ""
var age = 0
}
{
"name": "HelloWorld",
"age": 18
}
已知Swift是有KVC的(個人猜測不走runtime)一汽,通過類型安全的KeyPath可以實現(xiàn)屬性的讀寫,例如:
var person = Person()
person[\Person.name] = "Hello"
其中的手寫的表達式\Person.name
就是WritableKeyPath<Person, String>
類型的一個keyPath,關(guān)于KVC可以參考官網(wǎng)介紹:Swift KVC
要從json的"name"讀出"HelloWorld"并寫入Person對象的name屬性中召夹,我們可以直接走以下步驟:
var per = Person()
let nameFromJson = json["name"] // any
let nameForPerson = nameFromJson as? String // 把any轉(zhuǎn)成String
if let nameForPerson = nameForPerson {
per[keyPath: \Person.name] = nameForPerson // 通過kvc寫入
}
反過來從Person對象讀取name屬性寫入json:
var json = [String: Any]()
let nameFromPerson = person[keyPath: \Person.name] // 通過kvc讀出String
let nameForJSON = nameFromPerson as Any
json["name"] = nameForJSON
以上兩個事例可以完成Person對象的name屬性與JSON的"name"相互轉(zhuǎn)化了岩喷,那么剩下的age屬性也按照這個套路走就行了。簡單的封裝一下监憎,可以滿足各種方法纱意。
把以下4個元素整合起來:
- key :例如上面JSON的
"name"
- keyPath :例如上面Person對象的
\Person.name
- JSON Any轉(zhuǎn)化為屬性類型,并寫入MODEL的方法
- 屬性類型轉(zhuǎn)化為JSON Any鲸阔,并寫入JSON的方法 (這一步就不細講了偷霉,請讀者舉一反三)
struct JSONKeyPathObject<Model> {
let key: String
let keyPath: PartialKeyPath<Model>
var setJsonValueToModel: (Any, inout Model) -> Void
// var setModelValueToJson: (Any, inout [String: Any]) -> Void // (這一步就不細講了,請讀者舉一反三)
}
從String褐筛、Int类少、Double、Bool等基本數(shù)據(jù)類型開始
很顯然渔扎,基本數(shù)據(jù)類型都可以提前設(shè)定好setJsonValueToModel方法硫狞,例如int、string晃痴、double残吩、bool直接走這一個初始化方法,并且方法的泛型Value就是KeyPath里的Value:
extension JSONKeyPathObject {
init<Value>(key: String, keyPath: WritableKeyPath<Model, Value>) where Value: BasicDataType {
self.key = key
self.keyPath = keyPath
self.setJsonValueToModel = { anyJsonValue, model in
guard let value = anyJsonValue as? Value else {
return
}
model[keyPath: keyPath] = value
}
}
}
protocol BasicDataType {
}
extension String: BasicDataType {}
extension Int: BasicDataType {}
extension Double: BasicDataType {}
extension Bool: BasicDataType {}
把Person內(nèi)的所有屬性組成JSONKeyPathObject數(shù)組倘核,遍歷完不就直接把JSON轉(zhuǎn)化完了嗎世剖?!
補充一下Person的內(nèi)容
struct Person {
var name = ""
var age = 0
func allKeyPathObjects() -> [JSONKeyPathObject<Person>] {
return [
.init(key: "name", keyPath: \Person.name),
.init(key: "age", keyPath: \Person.age),
]
}
}
遍歷數(shù)組笤虫,給person對象寫入屬性值
let json: [String: Any] = [
"name": "HelloWorld",
"age": 18,
]
var person = Person()
for keyPathObject in person.allKeyPathObjects() {
keyPathObject.setJsonValueToModel(json[keyPathObject.key], &person)
}
print(person)
打印結(jié)果如下,復(fù)合預(yù)期
Person(name: "HelloWorld", age: 18)
這樣的散裝代碼還是再封裝一下比較好用祖凫,直接定義一個新協(xié)議琼蚯,就叫JSONable吧,必須實現(xiàn)allKeyPathObjects方法惠况,以及decodeFromJSON和encodeToJSON遭庶。(同樣的encodeToJSON不做詳細介紹,可以舉一反三)
protocol JSONable {
func allKeyPathObjects() -> [JSONKeyPathObject<Self>]
mutating func decodeFromJSON(_ json: [String: Any])
}
extension JSONable {
mutating func decodeFromJSON(_ json: [String: Any]) {
for keyPathObject in allKeyPathObjects() {
if let jsonValue = json[keyPathObject.key] {
keyPathObject.setJsonValueToModel(jsonValue, &self)
}
}
}
}
遞歸子MODEL轉(zhuǎn)化
既然我們有了JSONable協(xié)議了稠屠,是不是再給JSONKeyPathObject拓展一個新的初始化方法就行了峦睡?!也就可以實現(xiàn)遞歸子MODEL了权埠?榨了?!
是的攘蔽,直接上手
extension JSONKeyPathObject
init<Value>(key: String, keyPath: WritableKeyPath<Model, Value>) where Value: JSONable {
self.key = key
self.keyPath = keyPath
self.setJsonValueToModel = { anyJsonValue, model in
guard let json2 = anyJsonValue as? [String: Any] else {
return
}
var newSubModel = Value()
newSubModel.decodeFromJSON(json2)
model[keyPath: keyPath] = newSubModel
}
}
}
因為泛型Value要創(chuàng)建實例Value()
龙屉,所以給協(xié)議JSONable補充一個init()方法
protocol JSONable {
init()
func allKeyPathObjects() -> [JSONKeyPathObject<Self>]
mutating func decodeFromJSON(_ json: [String: Any])
}
寫一個嵌套模型試一下,給Person補充一個屬性Pet满俗,也是JSONable的
struct Person: JSONable {
var name = ""
var age = 0
var pet = Pet()
func allKeyPathObjects() -> [JSONKeyPathObject<Person>] {
return [
.init(key: "name", keyPath: \Person.name),
.init(key: "age", keyPath: \Person.age),
.init(key: "pet", keyPath: \Person.pet),
]
}
struct Pet: JSONable {
var type = ""
var weight = 0
func allKeyPathObjects() -> [JSONKeyPathObject<Pet>] {
return [
.init(key: "type", keyPath: \Pet.type),
.init(key: "weight", keyPath: \Pet.weight),
]
}
}
}
let json: [String: Any] = [
"name": "HelloWorld",
"age": 18,
"pet": [
"type": "dog",
"weight": 20,
],
]
var person = Person()
person.decodeFromJSON(json)
print(person)
打印結(jié)果正常
Person(name: "HelloWorld", age: 18, pet: MyJSONableLite.Person.Pet(type: "dog", weight: 20))
以上就把遞歸子MODEL也賦值上去了
Array转捕、Dictionary怎么辦作岖?Optional包裝類型又怎么辦
很簡單,再拓展JSONKeyPathObject五芝,以Array<JSONable>舉例
extension JSONKeyPathObject {
init<Value>(key: String, keyPath: WritableKeyPath<Model, Array<Value>>) where Value: JSONable {
self.key = key
self.setJsonValueToModel = { anyJsonValue, model in
guard let jsonArray = anyJsonValue as? [[String: Any]] else {
return
}
model[keyPath: keyPath] = jsonArray.map { json2 in
var newSubModel = Value()
newSubModel.decodeFromJSON(json2)
return newSubModel
}
}
}
}
其他的都差不多痘儡,改寫一下WritableKeyPath類型和setJsonValueToModel方法即可,可以舉一反三枢步。
從Macro結(jié)束
到這里讀者們肯定要反駁:這個要手寫allKeyPathObjects方法沉删,太麻煩了,要手寫key字符串价捧,寫錯了就出大bug了丑念,我才不想用。
我想說:且慢结蟋!還有一計脯倚!
從Swift 5.9開始(Xcode15),也有Macro的支持了嵌屎,借助宏的力量推正,可以幫我們自動補充allKeyPathObjects代碼!
反復(fù)研究我們的MODEL定義宝惰,發(fā)現(xiàn)只要拿到以下元素植榕,就可以組裝我們的allKeyPathObjects代碼
- MODEL的名稱,例如
Person
- 每一個Property的名稱尼夺,例如
name
- 拼裝KeyPath尊残,例如
\Person.name
struct Person: JSONable {
var name = ""
var age = 0
func allKeyPathObjects() -> [JSONKeyPathObject<Person>] {
return [
.init(key: "name", keyPath: \Person.name),
.init(key: "age", keyPath: \Person.age),
]
}
}
宏的實現(xiàn)比較復(fù)雜,可以直接查看成品Github地址淤堵,等后續(xù)將會補充part2專門研究這個宏怎么寫的
最終在最前面標(biāo)記我們的宏寝衫,讓編譯器完成代碼插入
@JSONableMacro
struct Person: JSONable {
var name = ""
var age = 0
}
完結(jié)
以上就是KVC實現(xiàn)的JSON轉(zhuǎn)MODEL方法,使用Macro更近一步減少手寫的代碼量拐邪。
有興趣的讀者還可以進一步實現(xiàn)Enum慰毅、Date等類型的轉(zhuǎn)化方法