XTIObjectMapper 14.2.1

XTIObjectMapper 14.2.1

无人认领 维护。



  • 作者
  • xt-input

XTIObjectMapper

XTIObjectMapper 是扩展 ObjectMapper 的框架,在 ObjectMapper 的基础上添加了浮点型数据的解析以及整型和浮点型数据对字符串的兼容。

ObjectMapper 是一个使用 Swift 编写的用于 model 对象(类和结构体)和 JSON 之间转换的框架。

特性

  • 将 JSON 映射成对象
  • 将对象映射成 JSON
  • 支持嵌套对象(可以是独立的成员变量、数组或字典中的元素)
  • 转换过程中支持自定义规则
  • 支持结构体(Struct)
  • 不可变支持(目前处于 beta 版本)

基础使用方法

为了支持映射,类或结构体只需实现 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: [AnyObject]?
    var dictionary: [String : AnyObject] = [:]
    var bestFriend: User?                       // 嵌套的 User 对象
    var friends: [User]?                        // Users 的数组
    var birthday: NSDate?

    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 字符串转换为 model 对象:

let user = User(JSONString: JSONString)

将 model 对象转换为 JSON 字符串:

let JSONString = user.toJSONString(prettyPrint: true)

您也可以使用 Mapper.swift 类(该类还提供了一些额外的函数来处理特殊情况)来完成转换:

// 把 JSON 字符串转成 Model
let user = Mapper<User>().map(JSONString: JSONString)
// 根据 Model 生成 JSON 字符串
let JSONString = Mapper().toJSONString(user, prettyPrint: true)

ObjectMapper 支持将以下类型映射到对象中:

  • Int
  • Bool
  • Double
  • Float
  • String
  • RawRepresentable(枚举)
  • Array<AnyObject>
  • Dictionary<String, AnyObject>
  • Object<T: Mappable>
  • Array<T: Mappable>
  • Array<Array<T: Mappable>>
  • Set<T: Mappable>
  • Dictionary<String, T: Mappable>
  • Dictionary<String, Array<T: Mappable>>
  • 以上所有的 Optional 类型
  • 以上所有的隐式强制解包类型(Implicitly Unwrapped Optional)

Mappable 协议

mutating func mapping(map: Map)

所有的映射最后都会调用这个函数。当解析 JSON 时,这个函数会在对象创建成功后被执行。当生成 JSON 时就只有这个函数会被对象调用。

init?(map: Map)

这个可失败的初始化函数是 ObjectMapper 创建对象时使用的。开发者可以通过这个函数在映射前校验 JSON。如果在这个方法中返回 nil,则不会执行 mapping 函数。可以通过传入的保存有 JSON 的 Map 对象进行校验:

required init?(map: Map){
	// 检查 JSON 里是否有一定要有的 "name" 属性
	if map.JSONDictionary["name"] == nil {
		return nil
	}
}

StaticMappable 协议

StaticMappable 是除了 Mappable 之外的选择,这个协议允许开发者通过一个静态函数来初始化对象,而不是通过 init?(map: Map)

注意:StaticMappableMappable 都继承了 BaseMappable 协议,其中 BaseMappable 协议声明了 mapping(map: Map) 函数。

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

ObjectMapper 使用这个函数获取对象后进行映射。开发者需要在这个函数中返回一个实现了 BaseMappable 对象的实例。这个函数也可以用于:

  • 在对象进行映射前校验 JSON
  • 提供一个缓存过的对象用于映射
  • 返回另一种类型的对象(当然,必须实现了 BaseMappable)用于映射。例如,你可能通过检查 JSON 推断出用于映射的对象(看这个例子)。

如果你需要在 extension 里实现 ObjectMapper,你则需要选择这个协议而不是 Mappable

ImmutableMappable 协议

使用 ImmutableMappable 可以映射不可变属性。以下表格展示了 ImmutableMappableMappable 的区别:

ImmutableMappable Mappable
Properties
let id: Int
let name: String?
var id: Int!
var name: String?
JSON -> Model
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"]
}
Model -> JSON
mutating func mapping(map: Map) {
  id   >>> map["id"]
  name >>> map["name"]
}
mutating func mapping(map: Map) {
  id   <- map["id"]
  name <- map["name"]
}
Initializing
try User(JSONString: JSONString)
User(JSONString: JSONString)

init(map: Map) throws

这个可能抛出异常的初始化函数用于在提供的 Map 中映射不可变属性。每个不可变的初始化属性都要在这个初始化函数中初始化。

当发生以下情况时,初始化函数会抛出一个错误:

  • Map 根据提供的键名获取不到对应的值
  • Map 使用 Transform 后没有得到值

ImmutableMappable 使用 Map.value(_:using:) 方法从 Map 中获取值。由于可能抛出异常,这个方法在使用时需要使用 try 关键字。 Optional 的属性可以用 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
}

mutating func mapping(map: Map)

此方法在将 Model 转回 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 的数组,可以通过以下方式访问:

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: "->"]
}

这种情况的 JSON 是这样的:

"com.myapp.info" : {
     "com.myapp.name" : "SwiftOldDriver"
}

自定义转换规则

ObjectMapper 也支持在映射时自定义转换规则。要使用自定义转换,创建一个包含 map["field_name"] 和你要使用的变换的 tuple(元组),放在 <- 的右边:

birthday <- (map["birthday"], DateTransform())

解析 JSON 时,上述转换会将 JSON 中的 Int 值转换为 NSDate,如果是对象转换为 JSON,则会将 NSDate 对象转换为 Int 值。

只需实现 TransformType 协议,就可以轻松创建自定义转换规则:

public protocol TransformType {
    associatedtype Object
    associatedtype JSON

    func transformFromJSON(_ value: Any?) -> Object?
    func transformToJSON(_ value: Object?) -> JSON?
}

TransformOf

大多数情况下,你都可以使用框架提供的转换类 TransformOf 快速实现期望的转换。 TransformOf 的初始化需要两个类型和两个闭包。两个类型声明了转换的目标类型和源类型,闭包实现了具体的转换逻辑。

例如,如果你想要将一个 JSON 字符串转换为 Int,你可以像这样使用 TransformOf

let transform = TransformOf<Int, String>(fromJSON: { (value: String?) -> Int? in 
    // 把值从 String? 转成 Int?
    return Int(value!)
}, toJSON: { (value: Int?) -> String? in
    // 把值从 Int? 转成 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 = "映射时需要知道的额外信息"
}

class User: Mappable {
	var name: String?
	
	required init?(map: Map){
	
	}
	
	func mapping(map: Map){
		if let context = map.context as? Context {
			// 获取到额外的信息
		}
	}
}

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 的类(ealm's List class。)

注意:使用 ObjectMappers 的 toJSON 函数生成 JSON 字符串只在 Realm 的写事务中有效(write transaction)。这是因为 ObjectMapper 在解析和生成时使用 inout 作为标记(flag),而 Realm 会检测到标记,并强制 toJSON 函数只能在一个写的事务中调用,即使这个对象并没有被修改。

待完成

  • 改善错误的处理。可能使用 throws 来处理。
  • 相关类的文档完善

安装

Cocoapods

如果你的项目使用 CocoaPods 0.36 及以上 的版本,你可以把以下内容添加到 Podfile 中,将 XTIObjectMapper 添加到你的项目中

pod 'XTIObjectMapper'

Carthage

如果你的项目使用 Carthage,你可以将以下内容添加到 Cartfile 中,将 XTIObjectMapper 的依赖添加到你的项目中:

github "xt-input/XTIObjectMapper"

Swift Package Manager

如果你的项目使用 Swift Package Manager,那么您可以将以下内容添加到 Package.swift 文件中的 dependencies 数组中,将 XTIObjectMapper 的依赖添加到项目中:

.Package(url: "https://github.com/xt-input/XTIObjectMapper.git", majorVersion: 2, minor: 2),

Submodule

此外,XTIObjectMapper 也可以作为一个 submodule 添加到项目中:

  1. 在终端中,使用 cd 命令进入项目文件的根目录,然后在终端中输入 git submodule add https://github.com/xt-input/XTIObjectMapper.git,将 XTIObjectMapper 添加为项目的一个 submodule
  2. 打开 XTIObjectMapper 文件,并将 XTIObjectMapper.xcodeproj 拖入到你 app 项目的文件导航中。
  3. 在 Xcode 中,点击蓝色项目图标进入 target 配置界面,在侧边栏的 "TARGETS" 下选择主工程对应的 target。
  4. 确保 XTIObjectMapper.framework 的部署版本(deployment target)与主工程的部署版本保持一致。
  5. 在配置界面的顶部选项栏中,打开 "Build Phases" 面板。
  6. 展开 "Target Dependencies" 组,并添加 XTIObjectMapper.framework
  7. 点击面板左上角的 + 按钮,选择 "New Copy Files Phase"。将这个阶段重命名为 "Copy Frameworks",设置 "Destination" 为 "Frameworks",最后添加 XTIObjectMapper.framework