原文:War on JSON in Swift (Object Mapper vs Codable)
Mapping
映射(Mapping)是一种操作,其中给定集合(域)中的每个元素与第二个集合(范围)的一个或多个元素相关联。
Mapping in Swift
通常,当 App 与外部 API 甚至有时是本地静态数据交互时,我们实际上会操作不同类型的数据,例如 JSON 或 plist,有时甚至是其他一些格式。我们需要用 JSON 数据映射我们的模型类对象,以便可以对我们的业务流程进行建模。在 Swift 中,我们有以下通用库用于解析和映射 JSON 与我们的模型对象:
- JSONSerialization
- SwiftyJSON ⭐️22.2k
- Codable
- ObjectMapper ⭐️9.1k
在这篇文章中,我们将了解 Swift 中最常见的两个解析和映射库,并将分析两者之间的区别。
ObjectMapper
ObjectMapper 是一个可以将 JSON 转换为对象(反之亦然)的框架。使用 ObjectMapper 之类的库可以更轻松、更快速地解析和映射 JSON。
为了使用 ObjectMapper,我们需要实现 Mappable 协议:
public protocol BaseMappable {
mutating func mapping(map: Map) {
}
}
public protocol Mappable: BaseMappable {
init?(map: Map)
}
对于使用 ObjectMapper:
- 我们的对象需要添加一个遵守
Mappable协议的扩展; - 我们的对象需要实现
mapping函数,在这个函数中我们将指定 JSON 的哪些属性分配给对象的哪些属性; - 属性必须声明为可选类型变量。
Codable
Codable 是 Swift4 标准库中引入的协议。它提供了三种类型:
Encodable协议:用于编码。Decodable协议:用于解码。Codable协议:用于编码和解码。
typealias Codable = Encodable & Decodable
对于使用 Codable:
- 自定义类型的编解码需要遵守
Codable协议。 - 自定义类型必须具有 Codable 类型的属性。
- 可编码类型包括
Int、Double、String、URL、Data等数据类型。 - 其他属性,如数组、字典,如果它们由可编码类型组成,则它们是可编码的。
ObjectMapper 和 Codable 的区别
ObjectMapper
- 这是一个第三方开源框架。
- ObjectMapper 支持类型转换。
- 据说 ObjectMapper 比 Codable 更快。(或者比 JSONEncoder/Decoder 更准确)
- 不承诺使用 ObjectMapper 对新的 Swift 版本进行更新。
- 需要在你的项目中添加额外的依赖项。
- ObjectMapper 是从 BaseMappable 协议进一步继承的协议。
- 需要使用 CocoaPods 或其他依赖管理器来更新版本。
- 需要为每个带有 JSON 键的字段定义映射。
Codable
Codable 的缺点:1. CodingKeys 只能映射一对一关系;2. 解码失败时会直接抛出异常,不支持设置默认值;2. 不支持自动类型转换;
- 这是 Swift 原生支持的解决方案。
- Codable 可能比 ObjectMapper 慢,因为它是内置的 mapping 解决方案。当你没有 coding keys 的时候,Swift 会用镜像读取类的属性,然后再进行映射。
- 在 Codable 中,我们需要额外的库进行类型转换(注:当需要映射的数据类型不一致时,Codable 不支持自动类型转换)。
- 消除了对第三方框架的依赖用于解析和映射。
- 如果模型对象字段与 JSON 键不同,我们需要使用
CodingKey协议将键定义为枚举。 - Codable 是一个协议,它是由另外两个协议组成的。Encodable & Decodable。
- 如果字段的名称与 JSON 中的键相同,就会自动生成
encode和init方法,所以需要的代码较少。 - 因为它是原生解决方案,所以在 Foundation 框架中可以获得更新。
示例
假设在我们的应用程序中,我们有一个 Customer 对象、一个 Address 对象、一个 Company 对象等。换句话说,像这样:
class Customer {
var id: Int?
var name: String?
var username: String?
var email: String?
var photo: String?
var company: Company?
var address: Address?
}
class Company {
var name: String?
var catchPhrase: String?
var bs: String?
}
class Address {
var street: String?
var suite: String?
var city: String?
var zipcode: String?
}
我们从远程 API 接收 JSON 数据并为我们的业务逻辑流填充这些模型对象。让我们分析一下使用 ObjectMapper 和 Codable 解析和映射这些模型对象。
使用 ObjectMapper
class Customer {
var id: Int?
var name: String?
var username: String?
var email: String?
var photo: String?
var company: Company?
var address: Address?
required init?(map: Map) {
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
username <- map["username"]
email <- map["email"]
phone <- map["phone"]
company <- map["company"]
address <- map["address"]
}
}
class Company {
var name: String?
var catchPhrase: String?
var bs: String?
required init?(map: Map) {
}
func mapping(map: Map) {
name <- map["name"]
catchPhrase <- map["catchPhrase"]
bs <- map["bs"]
}
}
class Address {
var street: String?
var suite: String?
var city: String?
var zipcode: String?
required init?(map: Map) {
}
func mapping(map: Map) {
street <- map["street"]
suite <- map["suite"]
city <- map["city"]
zipcode <- map["zipCodeForAddress"]
}
}
使用 Codable
class Customer: Codable {
var id: Int?
var name: String?
var username: String?
var email: String?
var photo: String?
var company: Company?
var address: Address?
}
class Company: Codable {
var name: String?
var catchPhrase: String?
var bs: String?
}
class Address: Codable {
var street: String?
var suite: String?
var city: String?
var zipcode: String?
// 自定义属性与 keys 的映射关系
private enum CodingKeys: String, CodingKey {
case street
case suite
case city
case zipcode = "zipCodeForAddress"
}
// 自定义编码
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(street, forKey: .street)
try container.encode(suite, forKey: .suite)
try container.encode(city, forKey: .city)
try container.encode(zipcode, forKey: .zipcode)
}
// 自定义解码
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
street = try container.decode(String?.self, forKey: .street)
suite = try container.decode(String?.self, forKey: .suite)
city = try container.decode(String?.self, forKey: .city)
zipcode = try container.decode(String?.self, forKey: .zipcode)
}
}
如你所见,在 Address 类中,我们定义了 custom keys,因为我们的 zipcode 字段与 zipCodeForAddress 不同。
总结
在我看来,Codable 是在 Swift 中映射模型对象的更优化和更好的方法,它是 Swift 原生解决方案并消除了第三方依赖。 ObjectMapper 仍然更受欢迎,因为它具有更好的可读性、流行度和对 Alamofire 的支持。在未来的时间里,我们希望在 iOS 开发者社区中看到更多使用 Codable 在 Swift 中进行解析和映射。