Swift4.0 Codable協(xié)議:JSON和模型的轉(zhuǎn)換

簡(jiǎn)單說(shuō)明

在OC中,以及Swift4.0之前,系統(tǒng)一直沒(méi)有一套數(shù)據(jù)解析的方法。在Swift4.0后,終于推出了Codable協(xié)議,可實(shí)現(xiàn)json數(shù)據(jù)和數(shù)據(jù)模型的相互轉(zhuǎn)換。

首先來(lái)看下 Codable ,它其實(shí)是一個(gè)組合協(xié)議,由 DecodableEncodable 兩個(gè)協(xié)議組成。

/// A type that can convert itself into and out of an external representation.
public typealias Codable = Decodable & Encodable

/// A type that can encode itself to an external representation.
public protocol Encodable {
    public func encode(to encoder: Encoder) throws
}

/// A type that can decode itself from an external representation.
public protocol Decodable {
    public init(from decoder: Decoder) throws
}

DecodableEncodable 分別是用來(lái)實(shí)現(xiàn)數(shù)據(jù)模型的解檔和歸檔。

數(shù)據(jù)模型只要遵循了 Codable 協(xié)議,就可以方便的進(jìn)行 JSON 數(shù)據(jù)和數(shù)據(jù)模型的相互轉(zhuǎn)換。

使用介紹

JSON 轉(zhuǎn) 模型

核心代碼:

JSONDecoder().decode(type: '某類型', from: 'Data數(shù)據(jù)')

例如我們有一個(gè)個(gè)人信息的 JSON 數(shù)據(jù),我們想要將其轉(zhuǎn)換為 Person 數(shù)據(jù)模型。

let jsonString =
"""
{
    "name":"LOLITA0164",
    "age":26,
    "address":"fuzhou"
}
"""

數(shù)據(jù)模型:

/// Persion模型,遵循 Codable 協(xié)議
class Person: Codable {
    var name: String?
    var age: Int?
    var address: String?
}

轉(zhuǎn)換過(guò)程:

// 將 json 字符串轉(zhuǎn)為 data 類型
if let jsonData = jsonString.data(using: String.Encoding.utf8) {
    if let person = try? JSONDecoder().decode(Person.self, from: jsonData){
        // 轉(zhuǎn)換成功,我們將數(shù)據(jù)輸出
        print(person.name!,person.age!,person.address!)
    }
}

輸出結(jié)果:

LOLITA0164 26 fuzhou

原理

一旦數(shù)據(jù)模型遵循了 Codable 協(xié)議,編譯器自動(dòng)會(huì)生成相關(guān)編碼和解碼的實(shí)現(xiàn)。
該協(xié)議中還有一個(gè)叫 CodingKey 的協(xié)議,用來(lái)表示編碼和解碼的key。

protocol CodingKey {
    var stringValue: String { get }
    init?(stringValue: String)
    var intValue: Int? { get }
    public init?(intValue: Int)
}

EncoderDecoder 是編碼器和解碼器,類似 OC 中的NSCoder。他們完成了數(shù)據(jù)的編碼和解碼工作。

當(dāng)我們的模型遵循 Codable 時(shí),編譯器實(shí)際上幫我們完成了下面的工作:

/// Persion模型,遵循 Codable 協(xié)議
class Person: Codable {
    var name: String?
    var age: Int?
    var address: String?
    // 編碼和解碼的所對(duì)應(yīng)的 key,編譯器會(huì)自動(dòng)生成成員變量的枚舉形式
    private enum CodingKeys: String, CodingKey {
        case name = "name"
        case age = "age"
        case address = "address"
    }
    // 解碼:JSON -> Model 必須實(shí)現(xiàn)這個(gè)方法
    required init(from decoder: Decoder) throws {
        // 解碼器提供了一個(gè)容器,用來(lái)存儲(chǔ)這些變量
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Int.self, forKey: .age)
        address = try container.decode(String.self, forKey: .address)
    }
    // 編碼:Model -> JSON 必須實(shí)現(xiàn)這個(gè)方法
    func encode(to encoder: Encoder) throws {
        // 編碼器同樣提供了一個(gè)容器,用來(lái)提供對(duì)應(yīng)變量的值
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(age, forKey: .age)
        try container.encode(address, forKey: .address)
    }
}

上述是編譯器自動(dòng)幫我們完成遵循 Codable 協(xié)議的數(shù)據(jù)模型的編碼和解碼過(guò)程,這些細(xì)節(jié)部分一般不需要我們關(guān)注。但是,在有些情況下,則需要我們自行實(shí)現(xiàn)相應(yīng)的方法。

  • 數(shù)據(jù)源和模型的成員變量不一致

在實(shí)際開發(fā)過(guò)程中,經(jīng)常遇到數(shù)據(jù)源和模型的成員變量不一致的情況,這種情況的出現(xiàn)通常是服務(wù)端和客戶端未達(dá)成統(tǒng)一,各自有不同的想法,又或者是開發(fā)的順序不一致,客戶端先于服務(wù)端完成導(dǎo)致字段不統(tǒng)一。無(wú)論那種情況,誰(shuí)去做改動(dòng)都是不合理的,那么當(dāng)客戶端想做兼容時(shí),就需要從 CodingKey 協(xié)議入手了。

例如服務(wù)端給了我們下面一串?dāng)?shù)據(jù):

sonString =
"""
{
    "NAME":"LOLITA0164",
    "AGE":26,
    "ADDRESS":"fuzhou"
}
"""

我們的數(shù)據(jù)模型依舊不變,這時(shí)我們調(diào)整一下 CodingKey

/// Persion模型,遵循 Codable 協(xié)議
class Person: Codable {
    var name: String?
    var age: Int?
    var address: String?
    
    /*
    注:
    1、一旦寫了CodingKey,需要將所有的成員都列出來(lái)(除非你只想解析其中部分字段),并且不能重復(fù)。
    2、CodingKeys是固定的枚舉的名稱,不能自定義。
     */
    private enum CodingKeys: String, CodingKey {
        case name = "NAME"
        case age = "AGE"
        case address = "ADDRESS"
    }
}

這樣,我們就可以正常解析 JSON 數(shù)據(jù)了。

  • 派生類

首先看個(gè)例子:

class Dog: Codable {
    var name: String?
}

class GoldenRetriever: Dog {
    var age: Float?
}

派生類的數(shù)據(jù)解析:

let jsonString =
"""
{
    "name":"kitty",
    "age":2.5,
}
"""
if let jsonData = jsonString.data(using: String.Encoding.utf8) {
    if let dog = try? JSONDecoder().decode(GoldenRetriever.self, from: jsonData){
        dump(dog)
    }
}

結(jié)果:

? JSONToModelSwift.GoldenRetriever #0
  ? super: JSONToModelSwift.Dog
    ? name: Optional("kitty")
      - some: "kitty"
  - age: nil

我們發(fā)現(xiàn),GoldenRetriever 類的實(shí)例只解析出了父類中的 name 字段,而本類中的 age 未能解析。這說(shuō)明,Codable 在繼承中是無(wú)效的,當(dāng)你在派生類中聲明遵循該協(xié)議時(shí),則會(huì)報(bào)錯(cuò):

Redundant conformance of 'GoldenRetriever' to protocol 'Decodable'
Redundant conformance of 'GoldenRetriever' to protocol 'Encodable'

這時(shí)候,就需要我們自行實(shí)現(xiàn) Codable 協(xié)議了。

class Dog: Codable {
    var name: String?
}

class GoldenRetriever: Dog {
    var age: Float?
    
    private enum CodingKeys: String, CodingKey {
        case name
        case age
    }
    // 這里只實(shí)現(xiàn)了解碼,需要編刪除線格式  碼時(shí),請(qǐng)自行參考之前的例子
    required init(from decoder: Decoder) throws {
        super.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Float.self, forKey: .age)
    }
}

結(jié)果:

? JSONToModelSwift.GoldenRetriever #0
  ? super: JSONToModelSwift.Dog
    ? name: Optional("kitty")
      - some: "kitty"
  ? age: Optional(2.5)
    - some: 2.5

模型 轉(zhuǎn) JSON

核心代碼:

JSONEncoder().encode('遵循 Encodable 的對(duì)象')

當(dāng)我們某個(gè)遵循 Codable 協(xié)議的對(duì)象想要轉(zhuǎn)為 JOSN 數(shù)據(jù)時(shí),我們則可以借助 JSONEncoder 編碼器來(lái)實(shí)現(xiàn)。

let p = Person()
p.name = "LOLITA0164"
p.age = 26
p.address = "fuzhou"
if let jsonData = try? JSONEncoder().encode(p) {
    // 編碼成功,將 jsonData 轉(zhuǎn)為字符輸出查看
    if let jsonString = String.init(data: jsonData, encoding: String.Encoding.utf8) {
        print("jsonString:" + "\(jsonString)")
    }
}

輸出結(jié)果:

jsonString:{"name":"LOLITA0164","age":26,"address":"fuzhou"}

JSON 轉(zhuǎn) 復(fù)雜數(shù)據(jù)模型

實(shí)際上,除了簡(jiǎn)單的數(shù)據(jù)模型,Codable 協(xié)議是能夠完成嵌套數(shù)據(jù)模型的轉(zhuǎn)換的。需要注意的是,嵌套的數(shù)據(jù)模型以及嵌套的子模型都必須遵循 Codable 協(xié)議。下面舉個(gè)例子來(lái)說(shuō)明。

假如我們有一個(gè)關(guān)于部門的數(shù)據(jù)模型,部門中有成員若干,可擁有管理者一名,其中的每一個(gè)人可能養(yǎng)了一只寵物狗。數(shù)據(jù)模型組成如下:

/// Department模型,也遵循 Codable 協(xié)議
class Department: Codable {
    var name: String
    var id: Int
    var members: [Person] = []
    var manager: Person?
}

/// Persion模型,遵循 Codable 協(xié)議
class Person: Codable {
    var name: String?
    var age: Int?
    var address: String?
    var aDog:Dog?
    private enum CodingKeys: String, CodingKey {
        case name = "NAME"
        case age = "AGE"
        case address = "ADDRESS"
        case aDog = "dog"
    }
}

/// Dog模型
class Dog: Codable {
    var name: String?
}

解析復(fù)雜數(shù)據(jù)模型

let jsonString =
    """
    {
        "name":"技術(shù)部",
        "id":123,
        "members":[
            {
                "NAME":"xiaoming",
                "AGE":24,
                "ADDRESS":"nanjing",
                "dog":{
                    "name":"Tom"
                }
            },
            {
                "NAME":"LOLITA0164",
                "AGE":26,
                "ADDRESS":"nanjing",
                "dog":{
                    "name":"Tonny"
                }
            },
        ],
        "manager":{
            "NAME":"ZHANG",
            "AGE":33,
            "ADDRESS":"nanjing",
        }
    }
    """
if let jsonData = jsonString.data(using: String.Encoding.utf8) {
    if let group = try? JSONDecoder().decode(Department.self, from: jsonData) {
        dump(group)
    }
}

結(jié)果:

? JSONToModelSwift.Department #0
  ? name: Optional("技術(shù)部")
    - some: "技術(shù)部"
  ? id: Optional(123)
    - some: 123
  ? members: 2 elements
    ? JSONToModelSwift.Person #1
      ? name: Optional("xiaoming")
        - some: "xiaoming"
      ? age: Optional(24)
        - some: 24
      ? address: Optional("nanjing")
        - some: "nanjing"
      ? aDog: Optional(JSONToModelSwift.Dog)
        ? some: JSONToModelSwift.Dog #2
          ? name: Optional("Tom")
            - some: "Tom"
    ? JSONToModelSwift.Person #3
      ? name: Optional("LOLITA0164")
        - some: "LOLITA0164"
      ? age: Optional(26)
        - some: 26
      ? address: Optional("nanjing")
        - some: "nanjing"
      ? aDog: Optional(JSONToModelSwift.Dog)
        ? some: JSONToModelSwift.Dog #4
          ? name: Optional("Tonny")
            - some: "Tonny"
  ? manager: Optional(JSONToModelSwift.Person)
    ? some: JSONToModelSwift.Person #5
      ? name: Optional("ZHANG")
        - some: "ZHANG"
      ? age: Optional(33)
        - some: 33
      ? address: Optional("nanjing")
        - some: "nanjing"
      - aDog: nil

我們可以看到,從使用上,無(wú)論解析簡(jiǎn)單的數(shù)據(jù)模型還是復(fù)雜的嵌套模型,在 JSON 轉(zhuǎn) Model 的使用方面都是一樣的,實(shí)際上,Model 轉(zhuǎn) JSON 也是一致的,大家可以嘗試一下。


問(wèn)題和改進(jìn)

雖然自定義 CodingKey 可以完成數(shù)據(jù)源和數(shù)據(jù)模型不一致的問(wèn)題(這和 OC 下的一些數(shù)據(jù)模型轉(zhuǎn)換采用的方式非常相似),但是在實(shí)際情況下,我們經(jīng)常遇到:數(shù)據(jù)模型相同,數(shù)據(jù)來(lái)源卻可能不一致,這導(dǎo)致一套 CodingKey 無(wú)法完成多種不同的編碼和解碼。那么一定要提前完成映射嗎?能否在拿到數(shù)據(jù)之后,進(jìn)行一次加工,將數(shù)據(jù)源處理成完全符合我們數(shù)據(jù)模型的標(biāo)準(zhǔn)再進(jìn)行數(shù)據(jù)轉(zhuǎn)換呢?答案是肯定的。
在 OC 的數(shù)據(jù)模型轉(zhuǎn)換中,筆者通過(guò) runtime 和 KVC 方式給數(shù)據(jù)模型賦值,以達(dá)到數(shù)據(jù)轉(zhuǎn)模型的目的,其中,映射字典是其中關(guān)鍵的一環(huán),目的就是通過(guò)映射字典將數(shù)據(jù)處理成標(biāo)準(zhǔn)的可直接 KVC 賦值的數(shù)據(jù),以此將數(shù)據(jù)轉(zhuǎn)模型變得更靈活。

我們先看下使用過(guò)程:

字典 轉(zhuǎn) 簡(jiǎn)單數(shù)據(jù)模型

首先依舊是 Person 類 和其數(shù)據(jù)源

/// Persion模型,遵循 Codable 協(xié)議
class Person: Codable {
    var name: String?
    var age: Int?
    var address: String?
}
// 數(shù)據(jù)字典
let dic_p:[String:Any] = [
    "Name":"LOLITA0164",
    "Age":26,
    "address":"fuzhou",
]

使用:

// 映射字典
// '模型字段':'數(shù)據(jù)源字段'
let dic_hint = [
    "name":"Name",
    "age":"Age"
]
// 轉(zhuǎn)換
if let p = try? LLModelTool.decode(Person.self, resDic: dic_p, hintDic: dic_hint) {
    dump(p)
}

結(jié)果:

? JSONToModelSwift.Person #0
  ? name: Optional("LOLITA0164")
    - some: "LOLITA0164"
  ? age: Optional(26)
    - some: 26
  ? address: Optional("fuzhou")
    - some: "fuzhou"

字典 轉(zhuǎn) 嵌套數(shù)據(jù)模型

依舊是上面的例子:假如我們有一個(gè)關(guān)于部門的數(shù)據(jù)模型,部門中有成員若干,可擁有管理者一名,其中的每一個(gè)人可能有養(yǎng)一只寵物狗。

/// Department模型,也遵循 Codable 協(xié)議
class Department: Codable {
    var name: String?
    var id: Int?
    var members: [Person] = []
    var manager: Person?
}

/// Persion模型,遵循 Codable 協(xié)議
class Person: Codable {
    var name: String?
    var age: Int?
    var address: String?
    var aDog:Dog?
}

/// Dog模型
class Dog: Codable {
    var name: String?
}


// 數(shù)據(jù)源
let dic_group: [String:Any] = [
    "NAME":"技術(shù)部",
    "ID":123,
    "MEMBERS":[
        [
            "Name":"小熊",
            "Age":25,
            "Address":"南京",
            "Dog":[
                "NameString":"kitty"
            ],
        ],
        [
            "Name":"LOLITA0164",
            "Age":26,
            "Address":"fuzhou"
        ]
    ],
    "Manager":[
        "name":"管理者",
        "age":33
    ]
]

使用:

// 映射字典
// '模型字段':'數(shù)據(jù)源字段'
let dic_hint2: [String:Any] = [
    // Department數(shù)據(jù)模型的映射關(guān)系
    "name":"NAME",
    "id":"ID",
    "members":"MEMBERS",
    // 嵌套模型的映射關(guān)系(key 對(duì)應(yīng)數(shù)據(jù)源中的 key)
    "MEMBERS":[
        // Person數(shù)據(jù)模型的映射關(guān)系
        "name":"Name",
        "age":"Age",
        "address":"Address",
        "aDog":"Dog",
        // 嵌套模型的映射關(guān)系(key 對(duì)應(yīng)數(shù)據(jù)源中的 key)
        "Dog":[
            // Dog數(shù)據(jù)模型的映射關(guān)系
            "name":"NameString"
        ]
    ],
    "manager":"Manager"
]

if let group = try? LLModelTool.decode(Department.self, resDic: dic_group, hintDic: dic_hint2) {
    dump(group)
}

結(jié)果:

? JSONToModelSwift.Department #0
  ? name: Optional("技術(shù)部")
    - some: "技術(shù)部"
  ? id: Optional(123)
    - some: 123
  ? members: 2 elements
    ? JSONToModelSwift.Person #1
      ? name: Optional("小熊")
        - some: "小熊"
      ? age: Optional(25)
        - some: 25
      ? address: Optional("南京")
        - some: "南京"
      ? aDog: Optional(JSONToModelSwift.Dog)
        ? some: JSONToModelSwift.Dog #2
          ? name: Optional("kitty")
            - some: "kitty"
    ? JSONToModelSwift.Person #3
      ? name: Optional("LOLITA0164")
        - some: "LOLITA0164"
      ? age: Optional(26)
        - some: 26
      ? address: Optional("fuzhou")
        - some: "fuzhou"
      - aDog: nil
  ? manager: Optional(JSONToModelSwift.Person)
    ? some: JSONToModelSwift.Person #4
      ? name: Optional("管理者")
        - some: "管理者"
      ? age: Optional(33)
        - some: 33
      - address: nil
      - aDog: nil

注:如果只有少數(shù)部分是不統(tǒng)一的,我們也可以通過(guò) CodingKey 將部分統(tǒng)一的字段編寫對(duì)應(yīng)關(guān)系,少數(shù)部分通過(guò)映射字典更換資源字典數(shù)據(jù),以完成轉(zhuǎn)換。

例如:

/// Persion模型,遵循 Codable 協(xié)議
class Person: Codable {
    var name: String?
    var age: Int?
    var address: String?
    // CodingKeys 只有兩個(gè)映射枚舉
    private enum CodingKeys: String, CodingKey {
        case name = "NAME"
        case age = "AGE"
        case address
    }
}

// 源字典中有第三個(gè)字段和 CodingKeys 中的不一致
let dic_p:[String:Any] = [
    "NAME":"LOLITA0164",
    "AGE":26,
    "ADDRESS":"fuzhou",
]
// 映射字典,只需映射不一致的即可
let dic_hint = [
    "address":"ADDRESS",
]
if let p = try? LLModelTool.decode(Person.self, resDic: dic_p, hintDic: dic_hint) {
    dump(p)
}

實(shí)現(xiàn)過(guò)程

首先,我們將 JSONDecoder().decode()進(jìn)行再次封裝:

/// 字典 轉(zhuǎn) 模型
static func decode<T>(_ type: T.Type, resDic: [String:Any] , hintDic:[String:Any]?) throws -> T where T: Decodable {
    var transformDic = resDic
    if (hintDic != nil) {
        // 將映射字典轉(zhuǎn)換成模型所需的字典
        transformDic = self.setUpResourceDic(resDic: resDic, hintDic: hintDic!)
    }
    guard let jsonData = self.getJsonData(param: transformDic) else {
        throw LLModelToolError.message("轉(zhuǎn)成 Data 時(shí)出錯(cuò)!!!")
    }
    guard let model = try? JSONDecoder().decode(type, from: jsonData)
        else {
        throw LLModelToolError.message("轉(zhuǎn)成 數(shù)據(jù)模型 時(shí)出錯(cuò)!!!")
    }
    return model
}

我們可以看到,該方法的核心依舊是系統(tǒng)的轉(zhuǎn)換方法,我們要做的就是將映射字典轉(zhuǎn)換成模型所需的字典,然后的處理一切照舊。

核心的轉(zhuǎn)換方法如下:

/// 根據(jù)映射字典設(shè)置當(dāng)前字典內(nèi)容
private static func setUpResourceDic(resDic: [String:Any] , hintDic:[String:Any]) -> [String:Any]{
    var transformDic = resDic
    for (key,value) in hintDic {
        let valueNew: AnyObject = value as AnyObject
        if valueNew.classForCoder == NSDictionary.classForCoder(){      // 模型映射
            let res_value = resDic[key] as AnyObject    // 為了獲取數(shù)據(jù)類型
            if res_value.classForCoder == NSArray.classForCoder(){  // 數(shù)據(jù)類型為數(shù)組(模型數(shù)組)
                let res_value_array = res_value as! [[String:Any]]
                var resArray: [Any] = []
                for item in res_value_array {
                    // 遞歸調(diào)用,尋找子模型
                    let res = self.setUpResourceDic(resDic: item , hintDic: valueNew as! [String : Any])
                    resArray.append(res)
                }
                let realKey = self.getRealKey(key: key, dic: hintDic)
                transformDic[realKey] = resArray
                // 移除舊的數(shù)據(jù)
                if realKey != key {
                    transformDic.removeValue(forKey: key)
                }
            }
            else if res_value.classForCoder == NSDictionary.classForCoder(){    // 數(shù)據(jù)類型為字典(模型)
                // 遞歸調(diào)用,尋找子模型
                let res = self.setUpResourceDic(resDic: res_value as! [String : Any] , hintDic: valueNew as! [String : Any])
                let realKey = self.getRealKey(key: key, dic: hintDic)
                transformDic[realKey] = res
                // 移除舊的數(shù)據(jù)
                if realKey != key {
                    transformDic.removeValue(forKey: key)
                }
            }
        }else if valueNew.classForCoder == NSString.classForCoder(){    // 普通映射
            // 去掉
            if !hintDic.keys.contains(valueNew as! String){
                transformDic[key] = resDic[valueNew as! String]
            }
            // 移除舊的數(shù)據(jù)
            if key != valueNew as! String {
                transformDic.removeValue(forKey: valueNew as! String)
            }
        }
    }
    return transformDic
}

轉(zhuǎn)換的思路為:

1、中心思想無(wú)非就是進(jìn)行 key 的替換

2、遍歷映射字典,如果映射字典中是 "String":"String" 我們直接進(jìn)行替換(先新增數(shù)據(jù),再將就數(shù)據(jù)刪除),如果是 "String":"Dictionary" ,則表示該字段中的 Dictionary 是一個(gè)數(shù)據(jù)模型,此時(shí)我們需要取出該字典,采用遞歸的方式深層次的尋找和替換。

缺點(diǎn)建議

復(fù)雜的數(shù)據(jù)模型在使用起來(lái)不是非常的順手,因?yàn)槲覀冃枰獮槠浼芯帉憦?fù)雜的對(duì)應(yīng)關(guān)系,因此不如將數(shù)據(jù)拆成簡(jiǎn)單的數(shù)據(jù)模型,再賦值給復(fù)雜模型,這樣映射字典變得簡(jiǎn)單很多,也更易閱讀。

完整的代碼為:

import Foundation

enum LLModelToolError: Error {
    case message(String)
}

struct LLModelTool {
    
    /// 字典 轉(zhuǎn) 模型
    static func decode<T>(_ type: T.Type, resDic: [String:Any] , hintDic:[String:Any]?) throws -> T where T: Decodable {
        // 將映射字典轉(zhuǎn)換成模型所需的字典
        var transformDic = resDic
        if (hintDic != nil) {
            transformDic = self.setUpResourceDic(resDic: resDic, hintDic: hintDic!)
        }
        guard let jsonData = self.getJsonData(param: transformDic) else {
            throw LLModelToolError.message("轉(zhuǎn)成 Data 時(shí)出錯(cuò)!!!")
        }
        guard let model = try? JSONDecoder().decode(type, from: jsonData)
            else {
            throw LLModelToolError.message("轉(zhuǎn)成 數(shù)據(jù)模型 時(shí)出錯(cuò)!!!")
        }
        return model
    }
    
    
    /// json 轉(zhuǎn)模型
    static func decode<T>(_ type: T.Type, jsonData: Data , hintDic:[String:Any]?) throws -> T where T: Decodable {
        guard let resDic: [String:Any] = try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String : Any] else {
            throw LLModelToolError.message("轉(zhuǎn)成 字典 時(shí)出錯(cuò)!!!")
        }
        return try! self.decode(type, resDic: resDic, hintDic: hintDic)
    }
    
    // 模型轉(zhuǎn)字典
    static func reflectToDict<T>(model: T) -> [String:Any] {
        let mirro = Mirror(reflecting: model)
        var dict = [String:Any]()
        for case let (key?, value) in mirro.children {
            dict[key] = value
        }
        return dict
    }
    
    
    
    /// 獲取 json 數(shù)據(jù),data類型
    static func getJsonData(param: Any) -> Data? {
        if !JSONSerialization.isValidJSONObject(param) {
            return nil
        }
        guard let data = try? JSONSerialization.data(withJSONObject: param, options: []) else {
            return nil
        }
        return data
    }


    /// 根據(jù)映射字典設(shè)置當(dāng)前字典內(nèi)容
    private static func setUpResourceDic(resDic: [String:Any] , hintDic:[String:Any]) -> [String:Any]{
        var transformDic = resDic
        for (key,value) in hintDic {
            let valueNew: AnyObject = value as AnyObject
            if valueNew.classForCoder == NSDictionary.classForCoder(){      // 模型映射
                let res_value = resDic[key] as AnyObject    // 為了獲取數(shù)據(jù)類型
                if res_value.classForCoder == NSArray.classForCoder(){  // 數(shù)據(jù)類型為數(shù)組(模型數(shù)組)
                    let res_value_array = res_value as! [[String:Any]]
                    var resArray: [Any] = []
                    for item in res_value_array {
                        // 遞歸調(diào)用,尋找子模型
                        let res = self.setUpResourceDic(resDic: item , hintDic: valueNew as! [String : Any])
                        resArray.append(res)
                    }
                    let realKey = self.getRealKey(key: key, dic: hintDic)
                    transformDic[realKey] = resArray
                    // 移除舊的數(shù)據(jù)
                    if realKey != key {
                        transformDic.removeValue(forKey: key)
                    }
                }
                else if res_value.classForCoder == NSDictionary.classForCoder(){    // 數(shù)據(jù)類型為字典(模型)
                    // 遞歸調(diào)用,尋找子模型
                    let res = self.setUpResourceDic(resDic: res_value as! [String : Any] , hintDic: valueNew as! [String : Any])
                    let realKey = self.getRealKey(key: key, dic: hintDic)
                    transformDic[realKey] = res
                    // 移除舊的數(shù)據(jù)
                    if realKey != key {
                        transformDic.removeValue(forKey: key)
                    }
                }
            }else if valueNew.classForCoder == NSString.classForCoder(){    // 普通映射
                // 去掉
                if !hintDic.keys.contains(valueNew as! String){
                    transformDic[key] = resDic[valueNew as! String]
                }
                // 移除舊的數(shù)據(jù)
                if key != valueNew as! String {
                    transformDic.removeValue(forKey: valueNew as! String)
                }
            }
        }
        return transformDic
    }
    
    
    /// 從映射字典中獲取到模型中對(duì)應(yīng)的key
    private static func getRealKey(key:String, dic:[String:Any]) -> String {
        for (k,v) in dic {
            let value: AnyObject = v as AnyObject
            if value.classForCoder == NSString.classForCoder(){
                let valueNew = value as! String
                if valueNew == key{
                    return k
                }
            }
        }
        return key
    }

}

補(bǔ)充說(shuō)明

在數(shù)據(jù)模型中的成員變量中,基本數(shù)據(jù)類型如:String、IntFloat等都已經(jīng)實(shí)現(xiàn)了 Codable 協(xié)議,因此如果你的數(shù)據(jù)類型只包含這些基本數(shù)據(jù)類型的屬性,只需要在類型聲明中加上 Codable 協(xié)議就可以了,不需要寫任何實(shí)際實(shí)現(xiàn)的代碼。

但是,一些特殊類型還有有一些限制

  • 枚舉

枚舉需要聲明原始值的類型,并且聲明遵循 Codable 協(xié)議。

enum Sex: String ,Codable {
    case female
    case male
}
// 數(shù)據(jù)模型
class People: Codable {
    var sex: Sex?
}

// 數(shù)據(jù)源
let dic: [String : Any] = [
    "sex":"male",
]
// 轉(zhuǎn)換
if let p = try? LLModelTool.decode(People.self, resDic: dic, hintDic: nil) {
    dump(p)
}

輸出:

? JSONToModelSwift.People #0
  ? sex: Optional(JSONToModelSwift.Sex.male)
    - some: JSONToModelSwift.Sex.male
  • 布爾型

Bool 類型默認(rèn)只支持 true/false 形式的 Bool 值解析。對(duì)于一些使用 0/1 形式來(lái)表示 Bool 值的后端框架,只能通過(guò) Int 類型解析之后再做轉(zhuǎn)換了,或者可以自定義實(shí)現(xiàn) Codable 協(xié)議。

enum Sex: String ,Codable {
    case female
    case male
}
// 數(shù)據(jù)模型
class People: Codable {
    var sex: Sex?
    var isTall: Bool? = nil
}

// 數(shù)據(jù)源
let dic: [String : Any] = [
    "sex":"male",
    "isTall":true
]
// 轉(zhuǎn)換
if let p = try? LLModelTool.decode(People.self, resDic: dic, hintDic: nil) {
    dump(p)
}

輸出:

? JSONToModelSwift.People #0
  ? sex: Optional(JSONToModelSwift.Sex.male)
    - some: JSONToModelSwift.Sex.male
  ? isTall: Optional(true)
    - some: true

參考

1、Swift 4 踩坑之 Codable 協(xié)議

2、Swift 4.0: Codable

3、Swift 中 class 怎么支持 Codable

4、swift4 字典->模型-轉(zhuǎn)換

三方轉(zhuǎn)換庫(kù)

這些庫(kù)我都沒(méi)有使用過(guò),僅僅是從其他人那邊摘抄過(guò)來(lái)做備份,讀者有興趣可以試一試。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容