美文网首页iOS、swift技术交流!
用Codable协议实现快速 JSON 解析

用Codable协议实现快速 JSON 解析

作者: 光明程辉 | 来源:发表于2018-07-07 16:54 被阅读3次

    Codable

    解释就是:可编码

    在 Swift 4 中新推出的 Codable 从根本上改进了使用 JSON 和其他数据表示方式的工作方式。
    Codable 提供了将 JSON 映射到 Swift 模型的简洁方法。
    得益于编译器的自动代码整合,
    Codable 协议的使用如果你是自定义也要遵循,
    Swift 系统库中的 String,Int,Double,Date,URL,Data 等

    你:“真的有那么神奇吗?”
    我:”是的“。

    例如:
    创建一个 Landmark 结构:(存储地标的名称和创始年份)

    struct Landmark {
    var name: String
    var foundingYear: Int
    }
    

    整合 Codable:(从Encodable和Decodable添加Codable到继承列表Landmark 自动处理满足所有的协议要求)

    struct Landmark: Codable {
     var name: String
    var foundingYear: Int
    }
    

    无需额外代码,JSON 的编码与解码就自动完成了。

    Swift标准库定义了数据编码和解码的标准化方法。

    你可以通过在自定义类型上实现EncodableDecodable协议来采用此方法。
    采用这些协议允许EncoderDecoder协议的实现接收你的数据,并将其编码或解码到外部表示(如JSON或属性列表)或从外部表示解码。为了支持编码和解码两者中,声明符合Codable,它结合了EncodableDecodable协议。这个过程被称为使你的类型可编码

    好了先看一下它的定义吧:

    Codable 协议的定义:
    typealias Codable = Decodable & Encodable
    

    它是 Protocol 的集合,也就是:

    Decodable 【作数据解析】和
    Encodable【用作数据编码】。

    声明一个实体类 Person 它声明实现了 Codable:

    struct Person : Codable {
    // 简单的几个属性声明
    var name: String
    var gender: String
    var age: Int
    }
    

    把它的实例编码成 JSON 字符串:

    let person = Person(name: "swift", gender: "male", age: 24)

    // 数据编码
    let encoder = JSONEncoder()
    let data = try! encoder.encode(person)
    let encodedString = String(data: data, encoding: .utf8)!
    print(encodedString) // 输出 json字符串:{"name":"swift","age":24,"gender":"male"}

    JSON 编码操作,是不是很简单:
    1、初始化了一个 Person 实例。
    2、初始化了一个 JSONEncoder。
    3、调用它的 encode 方法,把 person 实例进行编码。

    上面的数据编码可以理解为服务器的返回json,如下:

    {
    "name":"swift",
    "age":24,
    "gender":"male"
    },
    {
    "name":"Object-C",
     "age":26,
    "gender":
    "male"
    }
    

    当我们拿到服务器返回的json时,应该怎么解析呢?

    let jsonString = "{"name":"swift","age":22,"gender":"female"}"
    let jsonData = jsonString.data(using: .utf8)!
    // 据解析
    let decoder = JSONDecoder()

    //据解析
    let result = try! decoder.decode(Person.self, from: jsonData)
    print(result)  // 输出: Person(name: "swift", gender: "female", age: 22)
    

    注意:
    1、给它的 decode 方法传入要解析的实例类型 Person.self
    2、添加解析数据对象 jsonData, 就完成了 JSON 数据的解析了。

    总结一下:
    只需要你的实体类属性名和 JSON 数据能够对应上,就可以实现内容的解析。 
    
    Codable自定义: 在属性里添加结构体
    // 给下面的 var location: 使用
    struct Coordinate: Codable {
    var latitude: Double
    var longitude: Double
    }
    
    struct Landmark: Codable {
    // Double, String, and Int 一切都符合Codable的定义
    var name: String
    var foundingYear: Int
    
    // 添加自定义Codable类型的属性,保持整体Codable一致性。
    var location: Coordinate
    }
    

    从上面可以看出添加 结构体 是可以的,那么数组呢?也可以。
    好吧,接下来看复杂一点的数据结构:

    struct Landmark: Codable {
    // 常规的属性
    var name: String
    var foundingYear: Int
    var location: Coordinate
    
    // 添加这些属性后,Landmark仍然可编码.
    var vantagePoints: [Coordinate]
    var metadata: [String: String]
    var website: URL?
    }
    

    单个 编码(Encode) 或解码(Decode)

    在某些情况下,你可能不需要Codable支持双向编码和解码。
    例如,某些应用程序只需要调用远程网络API,而不需要解码包含相同类型的响应。
    Encodable如果你只需要支持数据编码Encode,则声明符合性。相反,Decodable如果你只需要读取给定类型的数据,则声明符合性。

    下面的示例显示Landmark,仅对数据进行编码或解码的结构的替代声明:

    //可编码
    struct Landmark: Encodable {
    var name: String
    var foundingYear: Int
    }
    
    // 解码
    struct Landmark: Decodable {
    var name: String
    var foundingYear: Int
    }
    
    编码 plist 数据格式

    PropertyListEncoder

    使用CodingKeys CodingKey对指定属性进行编码

    默认情况下,如果声明继承了 Codable 协议,这个实例中的所有属性都会被算作编码范围内。

    问题一 ?

    如果你只想对一部分属性进行编解码,怎么办呢?

    答:你可以在自定义类中声明一个 CodingKeys 枚举属性:
    struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    var vantagePoints: [Coordinate]
    
    // 声明一个 CodingKeys 枚举属性
    enum CodingKeys: String, CodingKey {
    
      // 1、只有在 CodingKeys 中指定的属性名才会进行编码
      //  2、这时的 name 会变为 ‘title’
        case name = "title"
        case foundingYear = "founding_date"
        
        case location
        case vantagePoints
      }
    }
    

    注意:
    那些没有在 CodingKeys 中声明的属性就必须要要有一个默认值。

    问题二 ?

    在开发中有时会遇到:序列化数据格式中使用的键与数据类型中的属性名称不匹配。怎么办?

    答:通过指定枚举String的原始值类型来处理。

    例如:
    使用 CodingKeys 改变编码属性的名称(看上面的代码)

    之前的解析结果是:
    {"name":"xxx","foundingYear":xxx, location:xxx, vantagePoints:xxx}
    最后的编码结果就是:
    {"title":"xxx","founding_date":xxx,location:xxx, vantagePoints:xxx}

    (自定义)手动编码和解码

    例如:给 Coordinate 添加一个属性 elevation,注意这里使用到了嵌套 层级关系,注意看 struct Coordinate { } 的括号范围。

    // 定义 Coordinate (坐标)
    struct Coordinate {
    var latitude: Double
    var longitude: Double
    // 海拔
    var elevation: Double
    
    enum CodingKeys: String, CodingKey {
        case latitude
        case longitude
        case additionalInfo
    }
    // 添加一个 additionalInfo 属性,该属性下可以有许多其它属性(添加海拔详细信息)
    enum AdditionalInfoKeys: String, CodingKey {
        case elevation
      }
    }
    

    这样的 结构是:

    {
    "latitude" : xxx
    "longitude": xxx
    "additionalInfo": {
        "elevation" : xxx
        "elevation2": xxx
      }
    }
    

    如果不给 elevation 一个 enum 的话,结构是:

    struct Coordinate {
    var latitude: Double
    var longitude: Double
    var elevation: Double
    
    enum CodingKeys: String, CodingKey {
    case latitude
    case longitude
    case additionalInfo
    case elevation
      }
     }
    

    解析的结果就是:(没有嵌套)

    {
    "latitude" : xxx
    "longitude": xxx
    "additionalInfo" : xxx
    "elevation": xxx
    }
    

    明白为啥添加多一个枚举之后,接着,回到主题。
    我们还需要手动的确立他们之间的对应关系:
    来个套路:首先建一个extension

    // init(from:)
    
    extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)
        
        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
      }
    }
    
     // encode(to:)
    extension Coordinate: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
        
        var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        try additionalInfo.encode(elevation, forKey: .elevation)
    }
    

    }

    上面的疑惑?

    • init(from decoder: Decoder) 用于解析数据。
    • encode(to encoder: Encoder) 方法用于编码数据。
    • decoder.container() 方法首先获取 CodingKey 的对应关系。
    • CodingKeys.self 表示我们先前声明的类型。
    • 用 values.decode() 方法解析单个属性。
    • values.nestedContainer() 方法获取内嵌的层级
    • 该encode(to:)方法的该实现反转了前一示例的解码操作。

    如果解析成功:

    {
        "latitude":"xxx",
        "longitude":xxx,
        "additionalInfo":{
                         "elevation":"xxx",
                         "elevation2":"xxx"
                    }
    }
    

    end

    数据解析,实际开发中是经常使用到的,大家务必掌握。

    最后给个API 给大家练习

    https://api.douban.com//v2/movie/in_theaters

    相关文章

      网友评论

        本文标题:用Codable协议实现快速 JSON 解析

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