ObjectMapper

CocoaPods Carthage compatible Swift Package Manager Build Status

ObjectMapper 是一个用 Swift 编写的框架,它可以让您轻松地将模型对象(类和结构体)转换为 JSON,以及从 JSON 转换回来。

特性

基础知识

为了支持映射,类或结构体只需要实现 Mappable 协议,该协议包含以下函数

init?(map: Map)
mutating func mapping(map: Map)

ObjectMapper 使用 <- 运算符来定义每个成员变量如何映射到 JSON 以及从 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 转换。

将 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 可以映射由以下类型组成的类

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 协议

StaticMappableMappable 的替代方案。它为开发人员提供了一个静态函数,ObjectMapper 使用该函数进行对象初始化,而不是 init?(map: Map)

注意:StaticMappable,就像 Mappable 一样,是 BaseMappable 的子协议,mapping(map: Map) 函数在 BaseMappable 中定义。

static func objectForMapping(map: Map) -> BaseMappable?

ObjectMapper 使用此函数来获取用于映射的对象。开发人员应在此函数中返回符合 BaseMappable 的对象实例。此函数还可以用于

如果您需要在扩展中实现 ObjectMapper,则需要采用此协议而不是 Mappable

ImmutableMappable 协议

ImmutableMappable 提供了映射不可变属性的功能。这是 ImmutableMappableMappable 的不同之处

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 映射不可变属性。每个不可变属性都应在此初始化器中初始化。

当以下情况时,此初始化器会抛出错误

ImmutableMappable 使用 Map.value(_:using:) 方法从 Map 获取值。此方法应与 try 关键字一起使用,因为它会抛出错误。可以使用 try? 轻松处理 Optional 属性。

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 Int 值转换为 Date,并在将对象转换为 JSON 时将 Date 转换为 Int。

您可以通过采用并实现 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"]
    }
}

如果您想序列化关联的 RealmObject,可以使用 ObjectMapper+Realm。这是一个简单的 Realm 扩展,它将任意 JSON 序列化到 Realm 的 List 类中。

要序列化 Swift StringIntDoubleBool 数组,您可以使用 ObjectMapperAdditions/Realm。它会将 Swift 类型包装到可以存储在 Realm 的 List 类中的 RealmValues 中。

注意:使用 ObjectMapper 的 toJSON 函数生成 Realm 对象的 JSON 字符串仅在 Realm 写入事务中有效。这是因为 ObjectMapper 在其映射函数 (<-) 中使用了 inout 标志,该标志既用于序列化又用于反序列化。Realm 检测到该标志,并强制 toJSON 函数在写入块内调用,即使对象未被修改。

使用 ObjectMapper 的项目

如果您有项目利用、扩展或为 ObjectMapper 提供工具,请提交 PR,并在 README 的此部分中包含指向您项目的链接。

待办事项

贡献

非常欢迎贡献 👍😃。

在提交任何拉取请求之前,请确保您已运行包含的测试并且它们已通过。如果您要包含新功能,也请为其编写测试用例。

安装

Cocoapods

可以使用 CocoaPods 0.36 或更高版本 将 ObjectMapper 添加到您的项目中,方法是将以下行添加到您的 Podfile

pod 'ObjectMapper', '~> 3.5' (check releases to make sure this is the latest version)

Carthage

如果您正在使用 Carthage,则可以通过将其添加到您的 Cartfile 中来添加对 ObjectMapper 的依赖

github "tristanhimmelman/ObjectMapper" ~> 3.5 (check releases to make sure this is the latest version)

Swift Package Manager

要将 ObjectMapper 添加到基于 Swift Package Manager 的项目中,请添加

.package(url: "https://github.com/tristanhimmelman/ObjectMapper.git", .upToNextMajor(from: "4.1.0")),

到您的 Package.swift 文件中的 dependencies 数组。

子模块

否则,可以将 ObjectMapper 添加为子模块

  1. 通过打开终端,cd 进入您的顶级项目目录,然后输入命令 git submodule add https://github.com/tristanhimmelman/ObjectMapper.git,将 ObjectMapper 添加为 子模块
  2. 打开 ObjectMapper 文件夹,并将 ObjectMapper.xcodeproj 拖到您的应用程序项目的文件导航器中。
  3. 在 Xcode 中,通过单击蓝色项目图标并选择侧边栏中“Targets”标题下的应用程序目标,导航到目标配置窗口。
  4. 确保 ObjectMapper.framework 的部署目标与应用程序目标的部署目标相匹配。
  5. 在该窗口顶部的选项卡栏中,打开“Build Phases”面板。
  6. 展开“Target Dependencies”组,并添加 ObjectMapper.framework
  7. 单击面板左上角的 + 按钮,然后选择“New Copy Files Phase”。将这个新阶段重命名为“Copy Frameworks”,将“Destination”设置为“Frameworks”,并添加 ObjectMapper.framework