ObjectMapper
ObjectMapper 是一个用 Swift 编写的框架,它使您能够轻松地将您的模型对象(类和结构体)与 JSON 进行动态转换。
- 特性
- 基础知识
- 映射嵌套对象
- 自定义转换
- 子类化
- 泛型对象
- 映射上下文
- ObjectMapper + Alamofire
- ObjectMapper + Realm
- 使用 ObjectMapper 的项目
- 待办事项
- 贡献
- 安装
特性
- 将 JSON 映射到对象
- 将对象映射到 JSON
- 嵌套对象(独立、数组中或字典中)
- 映射过程中的自定义转换
- 支持结构体
- 支持不可变类型
基础知识
为了支持映射,一个类或结构体只需要实现 Mappable
协议,该协议包括以下函数
init?(map: Map)
mutating func mapping(map: Map)
ObjectMapper 使用 <-
操作符来定义每个成员变量如何映射到和从 JSON 中。
class User: Mappable {
var username: String?
var age: Int?
var weight: Double!
var array: [Any]?
var dictionary: [String : Any] = [:]
var bestFriend: User? // Nested User object
var friends: [User]? // Array of Users
var birthday: Date?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
username <- map["username"]
age <- map["age"]
weight <- map["weight"]
array <- map["arr"]
dictionary <- map["dict"]
bestFriend <- map["best_friend"]
friends <- map["friends"]
birthday <- (map["birthday"], DateTransform())
}
}
struct Temperature: Mappable {
var celsius: Double?
var fahrenheit: Double?
init?(map: Map) {
}
mutating func mapping(map: Map) {
celsius <- map["celsius"]
fahrenheit <- map["fahrenheit"]
}
}
一旦您的类实现了 Mappable
,ObjectMapper 允许您轻松地将对象与 JSON 进行转换。
将JSON字符串转换为模型对象
let user = User(JSONString: JSONString)
将模型对象转换为JSON字符串
let JSONString = user.toJSONString(prettyPrint: true)
或者,可以使用Mapper.swift
类来完成上述操作(它还提供了其他情况的额外功能)
// Convert JSON String to Model
let user = Mapper<User>().map(JSONString: JSONString)
// Create JSON String from Model
let JSONString = Mapper().toJSONString(user, prettyPrint: true)
ObjectMapper可以映射以下类型的类
Int
Bool
Double
Float
String
RawRepresentable
(枚举)Array<Any>
Dictionary<String, Any>
Object<T: Mappable>
Array<T: Mappable>
Array<Array<T: Mappable>>
Set<T: Mappable>
Dictionary<String, T: Mappable>
Dictionary<String, Array<T: Mappable>>
- 所有上述的可选类型
- 上述类型的隐式解包可选类型
Mappable
协议
mutating func mapping(map: Map)
此函数是应当放置所有映射定义的地方。在解析JSON后,此函数会在对象创建成功后执行。在生成JSON时,这是唯一在对象上被调用的函数。
init?(map: Map)
这个可失败的初始化器由ObjectMapper用于对象创建。开发人员可以使用它来在对象序列化之前验证JSON。在函数中返回nil将阻止映射发生。您可以检查保存在Map
对象中的JSON来进行验证。
required init?(map: Map){
// check if a required "name" property exists within the JSON.
if map.JSON["name"] == nil {
return nil
}
}
StaticMappable
协议
StaticMappable
是Mappable
的替代方案。它为开发者提供了一个静态函数,用于ObjectMapper对象初始化,而不是使用init?(map: Map)
。
注意:StaticMappable
,就像Mappable
一样,是BaseMappable
的子协议,在BaseMappable
中定义了mapping(map: Map)
函数。
静态 func objectForMapping(map: Map) -> BaseMappable?
ObjectMapper 使用这个函数来获取用于映射的对象。开发者在函数中应返回一个符合 BaseMappable
的对象的实例。此函数还可以用于
- 在对象序列化之前验证 JSON
- 提供可用于映射的现有缓存对象
- 返回用于映射的其他类型(也符合
BaseMappable
)的对象。例如,您可能需要检查 JSON 以推断应使用哪种类型的对象进行映射(请参阅 ClassClusterTests.swift 中的示例)
如果您需要在扩展中实现 ObjectMapper,则需要采用此协议而不是 Mappable
协议。
ImmutableMappable
协议
ImmutableMappable
提供了映射不可变属性的能力。这就是 ImmutableMappable
和 Mappable
的不同之处
ImmutableMappable | Mappable |
---|---|
属性 | |
let id: Int let name: String? |
var id: Int! var name: String? |
JSON -> 模型 | |
init(map: Map) throws { id = try map.value("id") name = try? map.value("name") } |
mutating func mapping(map: Map) { id <- map["id"] name <- map["name"] } |
模型 -> JSON | |
func mapping(map: Map) { id >>> map["id"] name >>> map["name"] } |
mutating func mapping(map: Map) { id <- map["id"] name <- map["name"] } |
初始化 | |
try User(JSONString: JSONString) |
User(JSONString: JSONString) |
init(map: Map) throws
此可抛出初始化器用于映射给定 Map
的不可变属性。每个不可变属性都应该在此初始化器中初始化。
当
Map
无法获取给定键的值时抛出错误Map
在使用Transform
转换值时失败
ImmutableMappable
使用 Map.value(_:using:)
方法从 Map
获取值。此方法应该使用 try
关键字,因为它可能会抛出错误。可以使用 try?
容易地处理可选属性。
init(map: Map) throws {
name = try map.value("name") // throws an error when it fails
createdAt = try map.value("createdAt", using: DateTransform()) // throws an error when it fails
updatedAt = try? map.value("updatedAt", using: DateTransform()) // optional
posts = (try? map.value("posts")) ?? [] // optional + default value
surname = try? map.value("surname", default: "DefaultSurname") // optional + default value as an argument
}
mutating func mapping(map: Map)
该方法执行反向转换操作(模型转换为JSON)。由于不可变属性无法与<-
运算符进行映射,因此开发者必须使用>>>
运算符定义反向转换。
mutating func mapping(map: Map) {
name >>> map["name"]
createdAt >>> (map["createdAt"], DateTransform())
updatedAt >>> (map["updatedAt"], DateTransform())
posts >>> map["posts"]
}
嵌套对象的简单映射
ObjectMapper支持在键中使用点表示法以方便地映射嵌套对象。给定以下JSON字符串
"distance" : {
"text" : "102 ft",
"value" : 31
}
您可以使用以下方式访问嵌套对象
func mapping(map: Map) {
distance <- map["distance.value"]
}
嵌套键也支持从数组中访问值。给定一个包含距离数组的JSON响应,值可以按以下方式访问
distance <- map["distances.0.value"]
如果您有一个包含.
的键,您可以选择禁用上述功能,如下所示
func mapping(map: Map) {
identifier <- map["app.identifier", nested: false]
}
当您有包含.
的嵌套键时,您可以通过以下方式传递自定义的嵌套键分隔符(#629)
func mapping(map: Map) {
appName <- map["com.myapp.info->com.myapp.name", delimiter: "->"]
}
自定义转换
ObjectMapper也支持自定义转换,在映射过程中转换值。要使用转换,只需在<-
运算符右侧创建一个包含map["field_name"]
和所选转换的元组即可。
birthday <- (map["birthday"], DateTransform())
上述转换将在读取JSON时将JSON整数值转换为Date,并在将对象转换为JSON时将Date转换为整数值。
您可以通过采用并实现TransformType
协议中的方法轻松创建自己的自定义转换。
public protocol TransformType {
associatedtype Object
associatedtype JSON
func transformFromJSON(_ value: Any?) -> Object?
func transformToJSON(_ value: Object?) -> JSON?
}
TransformOf
在许多情况下,您可以使用内置的转换类TransformOf
快速执行所需的转换。TransformOf
使用两个类型和两个闭包进行初始化。类型定义转换将转换为何种类型以及从何种类型转换,闭包执行实际的转换。
例如,如果您想将JSON String
值转换为Int
,您可以使用TransformOf
如下所示
let transform = TransformOf<Int, String>(fromJSON: { (value: String?) -> Int? in
// transform value from String? to Int?
return Int(value!)
}, toJSON: { (value: Int?) -> String? in
// transform value from Int? to String?
if let value = value {
return String(value)
}
return nil
})
id <- (map["id"], transform)
以下是上述代码的更简洁版本
id <- (map["id"], TransformOf<Int, String>(fromJSON: { Int($0!) }, toJSON: { $0.map { String($0) } }))
子类
实现Mappable
协议的类可以轻松地进行子类化。当子类化可映射类时,请遵循以下结构
class Base: Mappable {
var base: String?
required init?(map: Map) {
}
func mapping(map: Map) {
base <- map["base"]
}
}
class Subclass: Base {
var sub: String?
required init?(map: Map) {
super.init(map)
}
override func mapping(map: Map) {
super.mapping(map)
sub <- map["sub"]
}
}
请确保您的子类实现调用了正确的初始化程序和映射函数,以便也能应用超类中的映射。
泛型对象
ObjectMapper可以处理带有泛型类型的类,只要泛型类型也符合Mappable
接口。请参见下面的示例。
class Result<T: Mappable>: Mappable {
var result: T?
required init?(map: Map){
}
func mapping(map: Map) {
result <- map["result"]
}
}
let result = Mapper<Result<User>>().map(JSON)
映射上下文
在映射过程中传递的Map
对象,包含一个可选的MapContext
对象,供开发人员使用,以便在映射过程中传递信息。
为了利用此功能,只需创建一个实现了MapContext
(这是一个空的协议)的对象,并在初始化过程中将其传递给Mapper
。
struct Context: MapContext {
var importantMappingInfo = "Info that I need during mapping"
}
class User: Mappable {
var name: String?
required init?(map: Map){
}
func mapping(map: Map){
if let context = map.context as? Context {
// use context to make decisions about mapping
}
}
}
let context = Context()
let user = Mapper<User>(context: context).map(JSONString)
ObjectMapper + Alamofire
如果您使用Alamofire进行网络连接,并希望将响应转换为Swift对象,则可以采用AlamofireObjectMapper。它是一个简单的Alamofire扩展,使用ObjectMapper自动将JSON响应数据映射到Swift对象。
ObjectMapper + Realm
ObjectMapper和Realm可以一起使用。只需遵循以下类结构,您就可以使用ObjectMapper生成您的Realm模型。
class Model: Object, Mappable {
dynamic var name = ""
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
name <- map["name"]
}
}
如果您想序列化关联的RealmObjects,可以使用ObjectMapper+Realm。它是一个简单的Realm扩展,将任意JSON序列化到Realm的List
类中。
要序列化Swift的String
、Int
、Double
和Bool
数组,您可以使用ObjectMapperAdditions/Realm。它会将Swift类型包装成可以在Realm的List
类中存储的RealmValues。
注意:生成Realm对象的JSON字符串只能使用ObjectMappers的toJSON
函数在Realm写入事务中进行。这是因为ObjectMapper使用映射函数中的(<-
)标志,这些标志既用于序列化又用于反序列化。Realm检测到该标志并强制在写入块中调用toJSON
函数,即使对象未被修改。
使用ObjectMapper的项目
如果您有一个使用、扩展或提供ObjectMapper工具的项目,请在此README部分的相应部分提交PR并提供您项目的链接。
待办事项
- 改进错误处理。或许可以尝试使用
throws
- 类簇文档
贡献
欢迎贡献力量
在提交任何pull请求之前,请确保已运行所包含的测试,并且它们已通过。如果您包括新功能,请为此编写测试用例。
安装
Cocoapods
使用CocoaPods 0.36或更高版本将ObjectMapper添加到您的项目中,方法是在您的Podfile
中添加以下行
pod 'ObjectMapper', '~> 3.5' (check releases to make sure this is the latest version)
Carthage
如果您使用的是 Carthage,可以通过将 ObjectMapper 添加到您的 Cartfile
来添加依赖
github "tristanhimmelman/ObjectMapper" ~> 3.5 (check releases to make sure this is the latest version)
Swift 包管理器
要向基于 Swift 包管理器 的项目添加 ObjectMapper,请将其添加到您的 Package.swift
文件中的 dependencies
数组。
.package(url: "https://github.com/tristanhimmelman/ObjectMapper.git", .upToNextMajor(from: "4.1.0")),
在您的 Package.swift
文件中添加以下内容:
子模块
否则,ObjectMapper 可以作为一个子模块添加
- 通过打开终端,切换到您的顶级项目目录,并输入命令
git submodule add https://github.com/tristanhimmelman/ObjectMapper.git
来将 ObjectMapper 作为子模块添加。 - 打开 ObjectMapper 文件夹,并将
ObjectMapper.xcodeproj
拖放到您的应用项目文件导航器中。 - 在 Xcode 中,通过点击蓝色项目图标,然后在侧栏中“Targets”标题下选择应用程序目标来导航到目标配置窗口。
- 确保
ObjectMapper.framework
的部署目标与应用程序目标的部署目标匹配。 - 在窗口顶部的标签栏中,打开“Build Phases”面板。
- 展开“Target Dependencies”组,并添加
ObjectMapper.framework
。 - 点击面板左上角的
+
按钮并选择“New Copy Files Phase”。将此新阶段重命名为“Copy Frameworks”,将“Destination”设置为“Frameworks”,并添加ObjectMapper.framework
。