swift Codable的使用及简单封装

swift在4.0之前,一直没有一套数据解析的方法。现在4.0后,终于有了Codable可以直接将json转成对象,有望取代OC的KVC机制。
先来看看Codable

public typealias Codable = Decodable & Encodable 

基本使用

它和NSCoding很像,可以重写decoder和encoder。基本的使用很简单

class Person: Codable {
      var name : String = ""
      var age : Int = 0
}

//json字符串
let JSONString = "{"name":"xiaoming","age":10}"
guard let jsonData = JSONString.data(using: .utf8) else {
        return
}
let decoder = JSONDecoder()
guard let obj = try? decoder.decode(Person.self, from: jsonData) else {
        return
}
print(obj.name) //xiaoming
print(obj.age)  //10

CodingKey

说到Codable就离不开CodingKey ,上面的例子只能适用于json数据与model属性一一对应的情况下使用,且对应的key也要一致才可以,而CodingKey可以解决开发中经常遇到的后台给与的key与app不一样的情况

class Person: Codable {
      var name : String = ""
      var age : Int = 0

      enum CodingKeys : String, CodingKey {
            case name = "name_a"
            case age
        }
}

//json字符串
let JSONString = "{"name_a":"xiaoming","age":10}"
guard let jsonData = JSONString.data(using: .utf8) else {
        return
}
let decoder = JSONDecoder()
guard let obj = try? decoder.decode(Person.self, from: jsonData) else {
        return
}
print(obj.name) //xiaoming

同时,如果遇到部分数据不需要接收的情况,CodingKey也可以处理,主要在CodingKeys的枚举中不要写这个key就可以了

class Person: Codable {
      var name : String = ""
      var age : Int = 0
      var score : Double = 0.00

      enum CodingKeys : String, CodingKey {
            case name = "name_a"
            case age
        }
}

//json字符串
let JSONString = "{"name_a":"xiaoming","age":10,"score":98.5}"
guard let jsonData = JSONString.data(using: .utf8) else {
        return
}
let decoder = JSONDecoder()
guard let obj = try? decoder.decode(Person.self, from: jsonData) else {
        return
}
print(obj.score) //0.00

封装

基于Codable的机制,可以简单封装一下,用于项目中。现在常见的网络框架,会直接把json数据转成字典,因此增加了字典转json和json转字典的方法。

//
//  Custom.swift
//  映射反射
//
//  Created by qiuchengxiang@gmail.com on 2018/1/3.
//  Copyright © 2018年 Zillion Fortune. All rights reserved.
//

import Foundation

fileprivate enum MapError: Error {
    case jsonToModelFail    //json转model失败
    case jsonToDataFail     //json转data失败
    case dictToJsonFail     //字典转json失败
    case jsonToArrFail      //json转数组失败
    case modelToJsonFail    //model转json失败
}

protocol Mappable: Codable {
    func modelMapFinished()
    mutating func structMapFinished()
}

extension Mappable {

    func modelMapFinished() {}

    mutating func structMapFinished() {}

    //模型转字典
    func reflectToDict() -> [String:Any] {
        let mirro = Mirror(reflecting: self)
        var dict = [String:Any]()
        for case let (key?, value) in mirro.children {
            dict[key] = value
        }
        return dict
    }


    //字典转模型
    static func mapFromDict(_ dict : [String:Any], _ type:T.Type) throws -> T {
        guard let JSONString = dict.toJSONString() else {
            print(MapError.dictToJsonFail)
            throw MapError.dictToJsonFail
        }
        guard let jsonData = JSONString.data(using: .utf8) else {
            print(MapError.jsonToDataFail)
            throw MapError.jsonToDataFail
        }
        let decoder = JSONDecoder()

        if let obj = try? decoder.decode(type, from: jsonData) {
            var vobj = obj
            let mirro = Mirror(reflecting: vobj)
            if mirro.displayStyle == Mirror.DisplayStyle.struct {
                vobj.structMapFinished()
            }
            if mirro.displayStyle == Mirror.DisplayStyle.class {
                vobj.modelMapFinished()
            }
            return vobj
        }
        print(MapError.jsonToModelFail)
        throw MapError.jsonToModelFail
    }


    //JSON转模型
    static func mapFromJson(_ JSONString : String, _ type:T.Type) throws -> T {
        guard let jsonData = JSONString.data(using: .utf8) else {
            print(MapError.jsonToDataFail)
            throw MapError.jsonToDataFail
        }
        let decoder = JSONDecoder()
        if let obj = try? decoder.decode(type, from: jsonData) {
            return obj
        }
        print(MapError.jsonToModelFail)
        throw MapError.jsonToModelFail
    }


    //模型转json字符串
    func toJSONString() throws -> String {
        if let str = self.reflectToDict().toJSONString() {
            return str
        }
        print(MapError.modelToJsonFail)
        throw MapError.modelToJsonFail
    }
}


extension Array {

    func toJSONString() -> String? {
        if (!JSONSerialization.isValidJSONObject(self)) {
            print("dict转json失败")
            return nil
        }
        if let newData : Data = try? JSONSerialization.data(withJSONObject: self, options: []) {
            let JSONString = NSString(data:newData as Data,encoding: String.Encoding.utf8.rawValue)
            return JSONString as String? ?? nil
        }
        print("dict转json失败")
        return nil
    }

    func mapFromJson(_ type:[T].Type) throws -> Array {
        guard let JSONString = self.toJSONString() else {
            print(MapError.dictToJsonFail)
            throw MapError.dictToJsonFail
        }
        guard let jsonData = JSONString.data(using: .utf8) else {
            print(MapError.jsonToDataFail)
            throw MapError.jsonToDataFail
        }
        let decoder = JSONDecoder()
        if let obj = try? decoder.decode(type, from: jsonData) {
            return obj
        }
        print(MapError.jsonToArrFail)
        throw MapError.jsonToArrFail
    }
}


extension Dictionary {
    func toJSONString() -> String? {
        if (!JSONSerialization.isValidJSONObject(self)) {
            print("dict转json失败")
            return nil
        }
        if let newData : Data = try? JSONSerialization.data(withJSONObject: self, options: []) {
            let JSONString = NSString(data:newData as Data,encoding: String.Encoding.utf8.rawValue)
            return JSONString as String? ?? nil
        }
        print("dict转json失败")
        return nil
    }
}


extension String {
    func toDict() -> [String:Any]? {
        guard let jsonData:Data = self.data(using: .utf8) else {
            print("json转dict失败")
            return nil
        }
        if let dict = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) {
            return dict as? [String : Any] ?? ["":""]
        }
        print("json转dict失败")
        return nil
    }
}

使用

enum BeerStyle : String, Codable {
    case ipa
    case stout
    case kolsch
    // ...
}



struct Beers: Mappable {

    var arrs : [Beer]
    var name : String

    mutating func structMapFinished() {
        name = "ngdgdfg"
    }

    struct Beer: Mappable {

        var name: String = "fsdf"
        var brewery: String = "fsdfsdf"
        var style: BeerStyle = .ipa
        var score: Double = 0.00
        var p: Person?



        enum CodingKeys : String, CodingKey {
            case name = "name_a"
            case brewery
            case p
            case score
        }


        class Person: Mappable {
            var name : String = ""
            var age : Int = 0
        }

    }
}


let dict1 = ["name_a":"nckjs","brewery":"gdfge","style":"stout","score":60.3,"p":["name":"jokh","age":10]] as [String : Any]
        let dict2 = ["name_a":"nckjs","brewery":"gdfge","style":"stout","score":60.3,"p":["name":"jokh","age":10]] as [String : Any]
        let dict3 = ["name_a":"nckjs","brewery":"gdfge","style":"stout","score":60.3,"p":["name":"jokh","age":10]] as [String : Any]

let arr = [dict1,dict2,dict3]
let dict4 = ["arrs" : arr, "name":"fgsdfs"] as [String : Any]
if let beers = try? Beers.mapFromDict(dict4, Beers.self) {
            print(beers)
}

这里增加了map完成后的回调方法,因为Codable的解析,并不是重新对象属性的set方法,和OC的KVC原理不一样。所以,在做一些赋值完成后的操作,需要使用这个回调方法。同时,需要注意区分class和struct的类型,选择对应的方法

null的处理

因为有人留言,问到后台可能每个值都会返回null的情况,又不想把属性设为可选的,所以增加了对null的处理,如果遇到null,就需要重新init(from decoder: Decoder) throws的方法

class Person: Codable {
            var name : String = ""
            var age : Int = 0

            init() {

            }

            required init(from decoder: Decoder) throws {
                let container = try decoder.container(keyedBy: CodingKeys.self)
                age = try container.decodeNil(forKey: .age) ? 10 : try container.decode(Int.self, forKey: .age)
                name = try container.decode(String.self, forKey: .name)
            }
        }

        let p = Person()
        let str = "{\"name\":\"fdfgdf\",\"age\":null}"
        let jsonData = str.data(using: .utf8)

        let decoder = JSONDecoder()

        if let obj = try? decoder.decode(Person.self, from: jsonData!) {
            print(obj.name,obj.age)
        }else {
            print("解析失败")
        }

decodeNil(forKey key: KeyedDecodingContainer.Key) throws -> Bool
这个方法就是判断在解析时,对应的属性是否是null值,如果是会返回true,如果不是null就会返回false

以上封装基本可以在一些中小型项目中使用,因为swift是一门强类型语言,使用时要注意json数据的类型要和model属性的类型一一对应,否则会解析失败。

参考:http://www.cocoachina.com/swift/20170630/19691.html

demo:https://github.com/NickQCX/CXMap/blob/master/README.md

你可能感兴趣的:(swift学习笔记,iOS)