Codable的Encoding和Decoding自定义类型
使您的数据类型可编码(encodable)和可解码(decodable),以与外部表示(如JSON)兼容。
概述
许多编程任务涉及通过网络连接发送数据,将数据保存到磁盘或将数据提交给API和服务。这些任务经常要求数据在传输过程中被编码和解码成中间格式。
Swift标准库定义了一种数据编码和解码的标准方法。您通过在自定义类型上实现Encodable和Decodable协议来采用这种方法。通过采用这些协议Encoder,Decoder协议和协议的实现可以获取数据,并将其编码或解码为外部表示(如JSON或属性列表)。为了同时支持编码和解码,声明符合Codable,它Encodable和Decodable协议相结合。这个过程被称为使你的类型可编码。
自动编码和解码
使一个类型可编码的最简单的方法是使用已经存在的类型来声明它的属性Codable。这些类型包括标准库类型,如String,Int和Double; 和地基类型一样Date,Data和URL。任何类型的属性都可以自动符合Codable只是声明一致性。
考虑一个Landmark存储地标名称和创建年份的结构:
struct Landmark {
var name: String
var foundingYear: Int
}
向触发器Codable的继承列表添加Landmark自动一致性,以满足来自Encodable和的所有协议要求Decodable:
struct Landmark: Codable {
var name: String
var foundingYear: Int
// Landmark now supports the Codable methods init(from:) and encode(to:),
// even though they aren't written as part of its declaration.
}
采用Codable您自己的类型,可以将它们序列化为任何内置数据格式,以及自定义编码器和解码器提供的任何格式。例如,即使Landmark结构本身不包含专门处理属性列表或JSON的代码,也可以使用和类进行编码。PropertyListEncoderJSONEncoderLandmark
同样的原则适用于由可编码的其他自定义类型组成的自定义类型。只要它的所有属性都是Codable,任何自定义类型都可以Codable。
下面的例子显示了Codable当一个location属性被添加到Landmark结构中时自动一致性的应用:
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
}
struct Landmark: Codable {
// Double, String, and Int all conform to Codable.
var name: String
var foundingYear: Int
// Adding a property of a custom Codable type maintains overall Codable conformance.
var location: Coordinate
}
内置的类型,如Array, Dictionary和Optional也顺应Codable只要它们含有可编码的类型。你可以添加一个Coordinate实例数组,Landmark整个结构仍然可以满足Codable。
下面的例子显示了在使用内置可编码类型添加多个属性时,自动一致性仍然如何Landmark:
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
// Landmark is still codable after adding these properties.
var vantagePoints: [Coordinate]
var metadata: [String: String]
var website: URL?
}
独有的编码或解码
在某些情况下,您可能不需要Codable对双向编码和解码的支持。例如,某些应用程序只需要调用远程网络API,而不需要解码包含相同类型的响应。Encodable如果您只需要支持数据编码,则声明符合。相反,Decodable如果您只需要读取给定类型的数据,则声明符合。
以下示例显示Landmark了仅对数据进行编码或解码的结构的替代声明:
struct Landmark: Encodable {
var name: String
var foundingYear: Int
}
struct Landmark: Decodable {
var name: String
var foundingYear: Int
}
选择属性以使用编码键(Coding Keys)进行编码(Encoding)和解码(Decoding)
Codable类型可以声明一个符合协议的特殊的嵌套枚举。当这个枚举出现时,它的情况就是当可编码类型的实例被编码或解码时必须包含的属性的权威列表。枚举个案的名称应该与您提供给您类型中相应属性的名称相匹配。CodingKeysCodingKey
如果在解码实例时不存在属性,或者某些属性不应包含在编码表示中,则省略枚举中的属性。被省略的属性需要默认值才能使其包含的类型自动符合或。CodingKeysCodingKeysDecodableCodable
如果序列化数据格式中使用的键不匹配数据类型中的属性名称,则通过指定枚举String的原始值类型来提供替代键。您用作每个枚举案例的原始值的字符串是编码和解码期间使用的密钥名称。案例名称与其原始值之间的关联使您可以根据“Swift API设计指南”为您的数据结构命名,而不必匹配您正在建模的序列化格式的名称,标点符号和大小写。CodingKeys
以下示例在编码和解码时使用结构的属性name和替代关键字:foundingYearLandmark
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
var vantagePoints: [Coordinate]
enum CodingKeys: String, CodingKey {
case name = "title"
case foundingYear = "founding_date"
case location
case vantagePoints
}
}
手动编码和解码
如果Swift类型的结构不同于其编码形式的结构,则可以提供自定义的实现Encodable并Decodable定义自己的编码和解码逻辑。
在下面的例子中,Coordinate扩展了结构以支持elevation嵌套在容器内的属性:additionalInfo
struct Coordinate {
var latitude: Double
var longitude: Double
var elevation: Double
enum CodingKeys: String, CodingKey {
case latitude
case longitude
case additionalInfo
}
enum AdditionalInfoKeys: String, CodingKey {
case elevation
}
}
由于Coordinate类型的编码形式包含第二层嵌套信息,因此类型采用Encodable和Decodable协议使用两个枚举,每个枚举列出在特定级别上使用的完整编码密钥集。
在下面的例子中,通过实现它所需要的初始化程序Coordinate来扩展结构以符合Decodable协议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)
}
}
初始化程序Coordinate通过使用Decoder它作为参数接收的实例上的方法来填充实例。该Coordinate实例的两个属性使用由雨燕标准库提供的容器密钥的API初始化。
下面的例子展示了如何通过实现其所需的方法Coordinate来扩展结构以符合Encodable协议encode(to:):
extension Coordinate: Encodable {
func encode(to encoder: Encoder) throws {
var = encoder.container(keyedBy: CodingKeys.self)
try.encode(latitude, forKey: .latitude)
try.encode(longitude, forKey: .longitude)
var additionalInfo =.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
try additionalInfo.encode(elevation, forKey: .elevation)
}
}