美文网首页
iOS 解码/编码

iOS 解码/编码

作者: gaookey | 来源:发表于2020-08-15 17:27 被阅读0次

    JSONEncoder / JSONDecoder

    一个类型通过声明自己遵守 Encodable 和/或 Decodable 协议,来表明可以被序列化和/或反序列化。这两个协议都只约束了一个方法,其中:Encodable 约束了 encode(to:),它定义了一个类型如何对自身进行编码;而 Decodable 则约束了一个初始化方法,用来从序列化的数据中创建实例:

    /// 一个类型可以将自身编码为某种外部表示形式。
    public protocol Encodable {
    /// 将值编码到给定的 encoder 中。
    public func encode(to encoder: Encoder) throws
    }
    /// 一个类型可以从某种外部表示形式中解码得到自身。
    public protocol Decodable {
    /// 从给定的 decoder 中解码来创建新的实例。
    public init(from decoder: Decoder) throws
    }
    

    Encoding / Decoding

    struct SPUserModel: Codable {
        var name: String
        var contact: SPContactModel?
    }
    struct SPContactModel: Codable {
        var mobileTelephone = ""
        var fixedTelephone = ""
    }
    

    Encoding

    let models = [SPUserModel(name: "zhangsan", contact: SPContactModel(mobileTelephone: "138xxxxxxxx", fixedTelephone: "010-xxxxxxx")),
                  SPUserModel(name: "lisi", contact: SPContactModel(mobileTelephone: "135xxxxxxxx", fixedTelephone: "020-xxxxxxx"))]
    
    do {
        let encoder = JSONEncoder()
        let jsonData = try encoder.encode(models)
        let jsonString = String(decoding: jsonData, as: UTF8.self)
        dump(jsonString)
    } catch { }
    

    Decoding

    do {
        let decoder = JSONDecoder()
        let decoded = try decoder.decode([SPUserModel].self, from: jsonData)
        dump(decoded)
    } catch { }
    

    合成的代码

    Coding Keys

    SPUserModel 里,编译器会生成一个叫做 CodingKeys 的私有枚举类型。这个枚举包含的成员与结构体中的存储属性一一对应。

    private enum CodingKeys: String, CodingKey {
        case name
        case contact
    }
    

    encode(to:) 方法

    下面是编译器为 SPUserModel 结构体生成的 encode(to:) 方法:

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(contact, forKey: .contact)
    }
    

    init(from:) 初始化方法

    当我们调用 try decoder.decode([SPUserModel].self, from: jsonData) 时,解码器会按照我们传入的类型 (这里是 [SPUserModel]),使用 Decodable 中定义的初始化方法创建一个该类型的实例。

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        contact = try container.decode(SPContactModel.self, forKey: .contact)
    }
    

    手动遵守协议

    自定义 Coding Keys

    我们可以创建自定义的 CodingKeys 枚举,在这个枚举中,我们可以:

    • 在编码后的输出中,用明确指定的字符串值重命名字段。
    • 将某个键从枚举中移除,以此跳过与之对应字段。

    想要设置一个不同的名字,我们需要明确将枚举的底层类型设置为 String。例如, API 数据某个字段 name 更改为与模型不匹配的字段 username,则需要自定义编码键,添加以下代码,枚举 CodingKeys 中包含 SPUserModel 模型中所有的属性,如此则可以正常解码。

    如果枚举里没有包含 name 键,因此编码时 name 将会被跳过,只有 contact 会被编码,被跳过的属性必须赋个默认值,不然将会编译失败。

    let json = """
    [{
        "username": "zhangsan",
        "contact": {"mobileTelephone": "138xxxxxxxx",
            "fixedTelephone": "010-xxxxxxx"
        }
    },
    {
        "username": "lisi",
        "contact": {"mobileTelephone": "138xxxxxxxx",
            "fixedTelephone": "010-xxxxxxx"
        }
    }]
    """
    
    struct SPUserModel: Codable {
        var name = ""
        var contact: SPContactModel?
        
        private enum CodingKeys: String, CodingKey {
            case name = "username"
            case contact
        }
    }
    struct SPContactModel: Codable {
        var mobileTelephone = ""
        var fixedTelephone = ""
    }
    
    do {
        let jsonData = json.data(using: .utf8)
        let decoder = JSONDecoder()
        let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
        dump(decoded)
    } catch { }
    

    自定义的 encode(to:) 和 init(from:) 实现

    JSONEncoderJSONDecoder 默认就可以处理可选值。当目标类型中的一个属性是可选值,如果数据中对应的值不存在的话,解码器将会正确地跳过这个属性。如下面的 contact 属性

    let json = """
    [{
        "name": "zhangsan"
    },
    {
        "name": "lisi"
    }]
    """
    
    struct SPUserModel: Codable {
        var name = ""
        var contact: SPContactModel?
    }
    struct SPContactModel: Codable {
        var mobileTelephone = ""
        var fixedTelephone = ""
    }
    

    如果数据和所期待的形式不同,则解码错误。 比如给 contact 对象一个空json对象

    let json = """
    [{
        "name": "zhangsan",
        "contact": { }
    },
    {
        "name": "lisi",
        "contact": { }
    }]
    """
    

    error: The data couldn’t be read because it is missing.

    do {
        let jsonData = json.data(using: .utf8)
        let decoder = JSONDecoder()
        let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
    } catch {
        //The data couldn’t be read because it is missing.
        print(error.localizedDescription)
    }
    

    重载 Decodable 的初始化方法 init(from:),明确地捕获我们所期待的错误,解码器就可以成功地解码这个错误的 JSON 了

    struct SPUserModel: Codable {
        var name = ""
        var contact: SPContactModel?
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.name = try container.decode(String.self, forKey: .name)
            do {
                self.contact = try container.decodeIfPresent(SPContactModel.self, forKey: .contact)
            } catch DecodingError.keyNotFound {
                self.contact = nil
            }
        }
    }
    struct SPContactModel: Codable {
        var mobileTelephone = ""
        var fixedTelephone = ""
    }
    

    常见的编码任务

    让其他人的代码满足 Codable

    假如 SPUserModel 中存在并不满足 Codable 协议的类,比如 CLLocationCoordinate2D ,编译器现在会 (正确地) 抱怨说它无法为 SPUserModel 自动生成实现 Codable 的代码,因为它的 coordinate 属性不再是遵从 Codable 的类型了。

    struct SPUserModel: Codable {
        var name = ""
        var coordinate: CLLocationCoordinate2D
    }
    

    解决办法1

    struct SPUserModel: Codable {
        var name = ""
        var coordinate: CLLocationCoordinate2D
        
        private enum CodingKeys: String, CodingKey {
            case name
            case latitude = "lat"
            case longitude = "lon"
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(name, forKey: .name)
            // 分别编码纬度和经度
            try container.encode(coordinate.latitude, forKey: .latitude)
            try container.encode(coordinate.longitude, forKey: .longitude)
        }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.name = try container.decode(String.self, forKey: .name)
            // 从纬度和经度重新构建 CLLocationCoordinate2D
            self.coordinate = CLLocationCoordinate2D (
                latitude: try container.decode(Double.self, forKey: .latitude),
                longitude: try container.decode(Double.self, forKey: .longitude)
            )
        }
    }
    
    let json = """
    [{
        "name": "zhangsan",
        "lat": 312312313,
        "lon": 3452423424
    },
    {
        "name": "lisi",
        "lat": 123132343,
        "lon": 3453432423
    }]
    """
    
    do {
        let jsonData = json.data(using: .utf8)
        let decoder = JSONDecoder()
        let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
        //Optional(__C.CLLocationCoordinate2D(latitude: 312312313.0, longitude: 3452423424.0))
        dump(decoded)
    } catch { }
    

    解决办法2:嵌套容器

    struct SPUserModel: Codable {
        var name = ""
        var coordinate: CLLocationCoordinate2D
        
        private enum CodingKeys: String, CodingKey {
            case name
            case coordinate
        }
        
        // 嵌套容器的编码键
        private enum CoordinateCodingKeys: CodingKey {
            case latitude
            case longitude
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(name, forKey: .name)
            var coordinateContainer = container.nestedContainer(keyedBy: CoordinateCodingKeys.self, forKey: .coordinate)
            try coordinateContainer.encode(coordinate.latitude, forKey: .latitude)
            try coordinateContainer.encode(coordinate.longitude, forKey: .longitude)
        }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.name = try container.decode(String.self, forKey: .name)
            let coordinateContainer = try container.nestedContainer(keyedBy: CoordinateCodingKeys.self, forKey: .coordinate)
            self.coordinate = CLLocationCoordinate2D (
                latitude: try coordinateContainer.decode(Double.self, forKey: .latitude),
                longitude: try coordinateContainer.decode(Double.self, forKey: .longitude)
            )
        }
    }
    
    let json = """
    [{
        "name": "zhangsan",
        "coordinate": {
            "latitude": 279886268,
            "longitude": 123678613
                      }
    },
    {
        "name": "lisi",
        "coordinate": {
            "latitude": 221311,
            "longitude": 67868
                      }
    }]
    """
    
    do {
        let jsonData = json.data(using: .utf8)
        let decoder = JSONDecoder()
        let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
        //Optional(__C.CLLocationCoordinate2D(latitude: 279886268.0, longitude: 123678613.0))
        dump(decoded)
    } catch { }
    

    解决办法3

    struct SPCoordinate: Codable {
        var latitude: Double
        var longitude: Double
    }
    
    struct SPUserModel: Codable {
        
        var name: String
        private var _coordinate: SPCoordinate
        var coordinate: CLLocationCoordinate2D {
            get {
                return CLLocationCoordinate2D(latitude: _coordinate.latitude,
                                              longitude: _coordinate.longitude)
            }
            set {
                _coordinate = SPCoordinate(latitude: newValue.latitude,
                                         longitude: newValue.longitude)
            }
        }
        private enum CodingKeys: String, CodingKey {
            case name
            case _coordinate = "coordinate"
        }
    }
    
    let json = """
    [{
        "name": "zhangsan",
        "coordinate": {
            "latitude": 279886268,
            "longitude": 123678613
                      }
    },
    {
        "name": "lisi",
        "coordinate": {
            "latitude": 221311,
            "longitude": 67868
                      }
    }]
    """
    
    do {
        let jsonData = json.data(using: .utf8)
        let decoder = JSONDecoder()
        let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
        //Optional(__C.CLLocationCoordinate2D(latitude: 279886268.0, longitude: 123678613.0))
        dump(decoded)
    } catch { }
    

    tips

    1. 属性样式转换(mobileTelephone -> mobile_telephone)

    假如 API 数据某个字段 mobileTelephone 更改为 mobile_telephone 样式,则会出现错误 :

    let json = """
    [{
        "name": "zhangsan",
        "contact": {"mobile_telephone": "138xxxxxxxx",
            "fixed_telephone": "010-xxxxxxx"
        }
    },
    {
        "name": "lisi",
        "contact": {"mobile_telephone": "138xxxxxxxx",
            "fixed_telephone": "010-xxxxxxx"
        }
    }]
    """
    
    do {
        let jsonData = json.data(using: .utf8)
        let decoder = JSONDecoder()
        let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
        print(decoded.first?.contact?.fixedTelephone)
    } catch {
        //error: The data couldn’t be read because it is missing.
        print(error.localizedDescription)
    }
    
    struct SPUserModel: Codable {
        var name: String
        var contact: SPContactModel?
    }
    struct SPContactModel: Codable {
        var mobileTelephone = ""
        var fixedTelephone = ""
    }
    

    在需要编码和解码的地方添加以下代码

    encoder.keyEncodingStrategy = .convertToSnakeCase
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    

    2.嵌套类型改变

    假如 API 数据嵌套类型更改,而我们仍然希望使用嵌套。

    let json = """
    [{
        "name": "zhangsan",
        "mobile_telephone": "138xxxxxxxx",
        "fixed_telephone": "010-xxxxxxx"
    },
    {
        "name": "lisi",
        "mobile_telephone": "138xxxxxxxx",
        "fixed_telephone": "010-xxxxxxx"
    }]
    """
    
    do {
        let jsonData = json.data(using: .utf8)
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
    } catch { }
    
    struct SPUserModel: Codable {
        var name: String
        var contact: SPContactModel?
        
        private enum CodingKeys: CodingKey {
            case name
            case mobileTelephone
            case fixedTelephone
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(name, forKey: .name)
            try container.encode(contact?.mobileTelephone, forKey: .mobileTelephone)
            try container.encode(contact?.fixedTelephone, forKey: .fixedTelephone)
        }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.name = try container.decode(String.self, forKey: .name)
            self.contact = SPContactModel (
                mobileTelephone: try container.decode(String.self, forKey: .mobileTelephone),
                fixedTelephone: try container.decode(String.self, forKey: .fixedTelephone)
            )
        }
    }
    struct SPContactModel: Codable {
        var mobileTelephone = ""
        var fixedTelephone = ""
    }
    

    3.日期的编解码

    encoder.dateEncodingStrategy = .formatted(<#T##DateFormatter#>)
    decoder.dateDecodingStrategy = .formatted(<#T##DateFormatter#>)
    

    假如我们希望日期的格式为 "yyyy-MM-dd"

    extension DateFormatter {
      static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter
      }()
    }
    
    struct SPUserModel: Codable {
        var name: String
        var birthday: Date?
    }
    

    需要设置解编码的 dateEncodingStrategy 属性

    let models = [SPUserModel(name: "zhangsan", birthday: Date()),
                  SPUserModel(name: "lisi", birthday: Date())]
            
    do {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .formatted(.dateFormatter)
        let jsonData = try encoder.encode(models)
        let jsonString = String(decoding: jsonData, as: UTF8.self)
    } catch { }
    
    do {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .formatted(.dateFormatter)
        let decoded = try decoder.decode([SPUserModel].self, from: self.jsonData)
    } catch { }
    

    PropertyListEncoder / PropertyListDecoder

    class SPUserModel: NSObject, Codable {
        var name: String
        var address: String
        
        init(name: String, address: String) {
            self.name = name
            self.address = address
        }
    }
    

    Encoding

    let models = [SPUserModel(name: "zhangsan", address: "beijing"),
                  SPUserModel(name: "lisi", address: "shanghai")]
    
    do {
        let data = try PropertyListEncoder().encode(models)
        let data2 = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true)
        UserDefaults.standard.set(data2, forKey: "key")
    } catch { }
    

    Decoding

    guard let data = UserDefaults.standard.object(forKey: "key") else { return }
    do {
        let data2 = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [SPUserModel.self], from: data as! Data)
        let model = try PropertyListDecoder().decode([SPUserModel].self, from: data2 as! Data)
        dump(model)
    } catch  { }
    

    --- ---

    Encoding

    let models = [SPUserModel(name: "zhangsan5", address: "beijing"),
                  SPUserModel(name: "lisi3", address: "shanghai")]
    
    do {
        let data = try PropertyListEncoder().encode(models)
        NSKeyedArchiver.archiveRootObject(data, toFile: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/info")
    } catch { }
    

    Decoding

    guard let data = NSKeyedUnarchiver.unarchiveObject(withFile: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/info") else { return }
    do {
        let model = try PropertyListDecoder().decode([SPUserModel].self, from: data as! Data)
    } catch  { }
    

    相关文章

      网友评论

          本文标题:iOS 解码/编码

          本文链接:https://www.haomeiwen.com/subject/pruadktx.html