DataMapper
介绍
DataMapper是一个框架,用于在不同的数据表示标准(目前我们支持JSON,但其他也可以轻松添加)之间安全地序列化和反序列化对象。
其优点包括:
- 易于使用的API
- 编译时安全性(尽可能的实现)
- 支持自定义序列化器(允许您通过实现一个类来简单地更改数据表示格式)
- 多态
- 线程安全(取决于您的使用情况)
- 支持单向使用(如果您不需要另一个方向,您不需要实现它)
变更日志
所有更改和新功能列表可以在此处找到。
要求
- Swift 4
- iOS 8+
安装
CocoaPods
数据映射器可以通过CocoaPods获得。要安装它,只需将以下行添加到Podfile中的测试目标。
pod "DataMapper"这将自动包含每个子规范(核心和所有序列化器)。
如果您想要不带序列化器的数据映射器(您有自己实现的),请使用
pod "DataMapper/Core"序列化器的每个实现都有自己的子规范。例如
pod "DataMapper/JsonSerializer"这些子规范以核心为依赖项,因此无需显式指定它。
使用方法
以下是此库提供的所有功能和如何使用它们。一些使用示例可以在测试中找到。
使用的术语
- 映射 - 反序列化或序列化
- 映射协议 -
Deserializable、Serializable或Mappable
快速概述
/*
[{
"number": 1,
"text": "A"
}, {
"number": 2,
"text": "B"
}]
*/
let inputData: NSData = ... // Some data in JSON to be deserialized.
let objectMapper = ObjectMapper()
let serializer = JsonSerializer()
// Deserialization
let type = serializer.deserialize(inputData)
let objects: [MyObject]? = objectMapper.deserialize(type)
... // Do some stuff with objects.
// Serialization
let changedType = objectMapper.serialize(objects)
let outputData = serializer.serialize(changedType)
// Can be deserilized and serialized.
struct MyObject: Mappable {
var number: Int?
var text: String?
init(_ data: DeserializableData) throws {
try mapping(data)
}
mutating func mapping(_ data: inout MappableData) throws {
data["number"].map(&number)
data["text"].map(&text)
}
}
// Can be only deserialized.
struct MyDeserializableObject: Deserializable {
let number: Int?
let text: String?
init(_ data: DeserializableData) throws {
number = data["number"].get()
text = data["text"].get()
}
}
// Can be only serialized.
struct MySerializableObject: Serializable {
let number: Int?
let text: String?
init(number: Int?, text: String?) {
self.number = number
self.text = text
}
func serialize(to data: inout SerializableData) {
data["number"].set(number)
data["text"].set(text)
}
}支持的类型
SupportedType在ObjectMapper和序列化器之间创建了一个中间级别。它是一个像枚举的结构(由于性能原因使用类实现),代表基本数据类型(null、string、bool、int、double、array、dictionary)。每种类型都有一个关联的属性,如果它是正确的类型,则返回其值,否则返回nil。对于null有一个小小的例外,其属性名为isNull,并返回Bool。
extension SupportedType {
var isNull: Bool
var string: String?
var bool: Bool?
var int: Int?
var double: Double?
var array: [SupportedType]?
var dictionary: [String: SupportedType]?
mutating func addToDictionary(key: String, value: SupportedType)
}例如
let type: SupportedType = .string("A")
type.string // "A"
type.number // niladdToDictionary将键值对添加到字典中。如果当前类型不是字典,则将其替换为一个新的字典。
SupportedType可以使用以下静态方法创建
extension SupportedType {
static var null: SupportedType
static func string(_ value: String) -> SupportedType
static func bool(_ value: Bool) -> SupportedType
static func int(_ value: Int) -> SupportedType
static func double(_ value: Double) -> SupportedType
static func array(_ value: [SupportedType]) -> SupportedType
static func dictionary(_ value: [String: SupportedType]) -> SupportedType
static func intOrDouble(_ value: Int) -> SupportedType
}intOrDouble 解决了在文本表示中数字模糊的问题。例如:1 是 Int 还是 Double。如果使用 intOrDouble(1) 创建 SupportedType,则 .int 和 .double 都返回 1。相反,如果你使用 .int(1),只有 .int 返回 1,而 .double 返回 nil。
ObjectMapper
final class ObjectMapper {
func serialize<T: Serializable>(_ value: T?) -> SupportedType
func deserialize<T: Deserializable>(_ type: SupportedType) -> T?
// + other overloads for supported types mentioned below
}ObjectMapper 将对象映射到 SupportedType。它有两种类型的方法
serialize- 接受 Swift 对象并将它们转换为SupportedType。deserialize- 接受SupportedType并将它们转换为 Swift 对象。
支持的 Swift 类型
T?[T]?[String: T]?[T?]?[String: T?]?
其中 T 符合 Map 协议(取决于方法)。如果 T 不符合此协议,则需要将 Transformation 实例作为名为 using 的第二个参数传递。
如你所见,deserialize 总是返回可选类型。如果 SupportedType 是 .null 或无法转换为 T,则返回 nil。
serialize 接受可选和非可选类型。如果传递 nil,则结果 SupportedType 是 .null。
[T]? 与 [T?]? 在反序列化中有所不同。如果数组中的某个元素是 nil(SupportedType 是 .null 或对象无法反序列化),则丢弃一切并返回 nil。在 [T?]? 的情况下,将添加 nil 值到数组中。同样适用于字典。
Serializer
protocol Serializer {
func serialize(_ supportedType: SupportedType) -> Data
func deserialize(_ data: Data) -> SupportedType
}Serializer 表示将 SupportedType 映射到 NSData 的某个对象。您无需实现 Serializer 就可以将 SupportedType 映射到 NSData,但是建议这样做,因为这样对象可以在其他库中使用。(此协议仅提供标准化 API。)
有时(几乎总是)与 String 一起工作比与 Data 一起工作更简单。因此,我们在 Serializer 中添加了扩展方法
extension Serializer {
func serialize(toString supportedType: SupportedType) -> String
func deserialize(fromString string: String) -> SupportedType
}注意:String 使用 UTF-8 编码转换为 Data(并返回)。
TypedSerializer
protocol TypedSerializer: Serializer {
associatedtype DataType
func typedSerialize(_ supportedType: SupportedType) -> DataType
func typedDeserialize(_ data: DataType) -> SupportedType
}使用泛型和方法扩展 Serializer。有时,您可能无法以 NSData,而是以 JSON(《Any》带有特定结构)的形式从其他库中获取数据,而这些数据的转换并不利于性能。
#### 预实现序列化器
JsonSerializer
正如其名所述,它支持与JSON一起使用。它符合 TypedSerializer 协议,而且 DataType 是 Any。对数据格式(《Any 或 NSJSONSerialization 相同。
Map 协议
可反序列化
protocol Deserializable {
init(_ data: DeserializableData) throws
}允许符合该协议的对象使用 ObjectMapper 从 SupportedType 反序列化。在这个 init 中,您需要使用 DeserializableData(参见 DeserializableData)初始化对象。如果由于某种原因无法创建对象(数据错误),则抛出 DeserializationError。
可序列化
protocol Serializable {
func serialize(to data: inout SerializableData)
}允许遵守该协议的对象使用 ObjectMapper 将其序列化为 SupportedType。在 serialize 中将您想要序列化的数据(不必要包含所有数据)设置为 SerializableData(参见 SerializableData)。
警告:如果序列化的数据是可变的,则此方法可能导致线程不安全(不可变性和结构是这里的帮手)。
Mappable 协议
protocol Mappable: Serializable, Deserializable {
mutating func mapping(_ data: inout MappableData) throws
}Mappable 协议结合了 Deserializable 和 Serializable。它为 serialize 提供默认实现,但需要手动实现 init,通常如下所示:
struct SomeObject: Mappable {
init(_ data: DeserializableData) throws {
try mapping(data)
}
...这也意味着在调用 mapping 方法之前必须初始化对象。
如果您更改了 init 或 serialize 的默认实现,不要忘记在 init 中调用 try mapping(data) 或在 serialize 中调用 mapping(&data)。
在 mapping 中,您有 MappableData(参见 MappableData)的访问权限,允许您指定如何将对象映射到一个地方。为此,字段必须是可变的。不可变字段需要单独在 init 和 serialize 中定义,如下所示:
struct SomeObject: Mappable {
let constant: Int?
var variable: Int?
init(_ data: DeserializableData) throws {
constant = data["constant"].get()
try mapping(data)
}
func serialize(to data: inout SerializableData) {
data["constant"].set(constant)
mapping(&data)
}
mutating func mapping(_ data: inout MappableData) throws {
data["variable"].map(&variable)
}
}抛出操作与 Deserializable 中相同。
警告:与 Serializable 相同的线程安全问题。
可序列化数据/序列化数据/可映射数据
它们在映射协议中对应的相应方法中使用。它们为特定的方法提供了一个许多重载和一个索引。这个索引用作字典中的键,它可以像这样嵌套
data["a"]["b"]
data[["a", "b"]]
data["a", "b"]这些全部意味着数据对应于一个键为 "a" 的字典,该字典的键为 "b"。
该方法具有与 ObjectMapper 相同类型的重载以及相同的操作行为(参见 ObjectMapper),并且对于每个重载,有三个选项
- 与 ObjectMapper 相同 - 与可选类型一起工作,nil 表示 .null
try- 与非可选类型一起工作,如果找到 .null 则抛出异常or- 与非可选类型一起工作,用 or 的值替换 .null
可序列化数据
DeserializableData 在 Deserializable 的 init 中使用。方法名为 get,从数据中检索值。使用方法
let value: Int? = data["value"].get()
let value: Int = try data["value"].get()
let value: Int = data["value"].get(or: 0)
let value: X? = data["value"].get(using: XTransformation())
let value: X = try data["value"].get(using: XTransformation())
let value: X = data["value"].get(using: XTransformation(), or: X())序列化数据
SerializableData 在 Serializable 的 serialize 中使用。方法名为 set,将值设置到数据中。使用方法
data["value"].set(value)
data["value"].set(value, using: XTransformation())注意:由于 set 可以接受可选和非可选类型,所以没有为 try 和 or 提供重载(没有必要,因为它们可以接受这两种类型)。
可映射数据
MappableData 在 Mappable 的 mapping 中使用。该方法名为 map,它根据上下文的行为类似于 get 或 set。使用方法
data["value"].map(&value) // var value: Int?
try data["value"].map(&value) // var value: Int
data["value"].map(&value, or: 0) // var value: Int
data["value"].map(&value, using: XTransformation()) // var value: X?
try data["value"].map(&value, using: XTransformation()) // var value: X
data["value"].map(&value, using: XTransformation(), or: X()) // var value: X注意:只有在反序列化过程中,try 和 or 才会影响 map 的结果。
转换
转换提供了一种指定对象如何映射的另一种方式。它们用于覆盖Map协议中方法的实现,或者允许映射不符合Map协议的类型。
它们有三种类型:仅用于反序列化的DeserializableTransformation,仅用于序列化的SerializableTransformation,以及两者都有的Transformation。所有这些特定的实现(如AnyTransformation、SupportedTypeConvertible等)都三个版本,名称相应。
了解如何创建新转换的最佳方式是查看现有代码。
预实现的转换
EnumTransformation- 使用RawRepresentableURLTransformation-String到NSURL(在init中可以指定使用相对或绝对路径)
日期类型
CustomDateFormatTransformation- 使用init中指定的formatString作为NSDateFormatter.dateFormatDateFormatterTransformation- 使用NSDateFormatterDateTransformation-Double作为timeIntervalSince1970ISO8601DateTransformation- ISO8601格式的String
值类型
BoolTransformationDoubleTransformationIntTranformationStringTransformation
AnyTransformation
AnyTransformation表示Swift协议中使用关联类型作为变量类型的模式。要将任何Transformation实例转换为它,只需调用transformation.typeErased()。这在下面提到的Transformation的特定实现中经常需要。
注意:AnyTransformation有仅反序列化或仅序列化的变体,它们也有typeErased()方法。所以有时可能需要显式指定此方法的输出类型。例如
let transformation = IntTransformation()
let anyTransformation: AnyTransformation = transformation.typeErased()
let anyDeserializableTransformation: AnyDeserializableTransformation = transformation.typeErased()
let anySerializableTransformation: AnySerializableTransformation = transformation.typeErased()SupportedTypeConvertible
通过SupportedTypeConvertible扩展类型提供Map协议的默认实现,如果该类型已经有转换。所有具有转换的值类型和NSURL都符合该协议。
以下是Int的一个示例实现
extension Int: SupportedTypeConvertible {
static var defaultTransformation = IntTransformation().typeErased()
}注意:这允许您直接在ObjectMapper中使用像Int这样的类型,而无需传递转换。
复合转换
复合转换允许您重用已存在的转换,以转换类型_transitiveObject到/自SupportedType。然后,您只需编写将_transitiveObject转换为/自Object的代码。
委托转换
委托转换与复合转换相似,因为它使用另一个转换,但在此之后没有其他转换。通常,这用于专门化更通用的转换。例如,这是ISO8601DateTransformation的实现。
struct ISO8601DateTransformation: DelegatedTransformation {
typealias Object = Date
let transformationDelegate = CustomDateFormatTransformation(formatString: "yyyy-MM-dd'T'HH:mm:ssZZZZZ").typeErased()
}因为已经有了处理转换为/自.string的CustomDateFormatTransformation,因此在这里重新实现它是不必要的。只需指定使用的格式即可。
多态
protocol Polymorph {
/// Returns type to which the supportedType should be deserialized.
func polymorphType<T>(for type: T.Type, in supportedType: SupportedType) -> T.Type
/// Write info about the type to supportedType if necessary.
func writeTypeInfo<T>(to supportedType: inout SupportedType, of type: T.Type)
}多态表示一个可以决定在运行时应将数据反序列化到哪个对象的对象,并在序列化到SupportedType时保留有关对象具体类型的元数据。
要使用多态,请用它与ObjectMapper进行初始化。例如:
let objectMapper = ObjectMapper(polymorph: StaticPolymorph())目前只有一个实现(StaticPolymorph),但是一旦Swift添加了反射,我们将实现新的一个(动态)。也欢迎您实现自己的多态性,如果我们的多态性不足以满足您的需求,您甚至可以选择“硬编码”应使用哪些类型。
以下是多态可以做什么的示例
class A: Mappable {
let value: Int?
...
}
class B: A {
let text: String?
...
}
struct MyPolymorph: Polymorph {
// If B is castable to T and supportedType contains a dictionary with key "type" and the value "B", then the type to use is `B`, otherwise does nothing.
func polymorphType<T>(for type: T.Type, in supportedType: SupportedType) -> T.Type {
if let bType = B.self as? T.Type, supportedType.dictionary?["type"]?.string == "B" {
return bType
}
return type
}
// If T is B, write info about it into supportedType.
func writeTypeInfo<T>(to supportedType: inout SupportedType, of type: T.Type) {
if type == B.self {
supportedType.addToDictionary(key: "type", value: .string("B"))
}
}
}
let objectMapper = ObjectMapper()
let objectMapperWithPolymorh = ObjectMapper(polymorph: MyPolymorph())
let aType: SupportedType = .dictionary(["value": .int(1)])
let bType: SupportedType = .dictionary(["value": .int(2), "text": .string("text"), "type": .string("B")])
// Deserialization
let aObject: A? = objectMapper.deserialize(aType) // A(value: 1) - no surprise here
let bObject: A? = objectMapper.deserialize(bType) // A(value: 2) - the rest of the dictionary is ignored
let aPolymorphic: A? = objectMapperWithPolymorh.deserialize(aType) // A(value: 1) - again the same result
let bPolymorphic: A? = objectMapperWithPolymorh.deserialize(bType) // B(value: 2, text: "text") - this time the polymorph comes into play
// Serialization
objectMapper.serialize(aObject) // .dictionary(["value": .int(1)])
objectMapper.serialize(bObject) // .dictionary(["value": .int(2)])
objectMapperWithPolymorh.serialize(aPolymorphic) // .dictionary(["value": .int(1)]) - so far no difference
objectMapper.serialize(bPolymorphic) // .dictionary(["value": .int(2), "text": .string("text")])
objectMapperWithPolymorh.serialize(bPolymorphic) // .dictionary(["value": .int(2), "text": .string("text"), "type": .string("B")]) - type is added静态多态
静态多态通过在supportedType中寻找具有特定键(用于确定键的键是由输入对象类型确定的)的字典条目来解决类型。然后,将该键的值与已知类型的名称进行比较。如果找到匹配项,则返回正确的类型,否则返回输入类型。当序列化时,StaticPolymorh将对应的序列化类型的多项式键值对添加到SupportedType中。
StaticPolymorph 只影响实现了 Polymorphic 协议的对象。对于其他类型,polymorphType 返回输入类型,而 writeTypeInfo 不执行任何操作。
注意:仅实现 Polymorphic 协议不足以使对象在 ObjectMapper 中使用。为了解决这个问题,存在类型别名,它们将 Polymorphic 与 Map 协议组合:PolymorphicDeserializable、PolymorphicSerializable 和 PolymorphicMappable。
注意:StaticPolymorph 的限制是只能使用类。无法使用协议和结构体。
多态
Polymorphic 的定义如下
protocol Polymorphic: AnyObject {
static var polymorphicKey: String { get }
static var polymorphicInfo: PolymorphicInfo { get }
}polymorphicKey 代表上述提到的键。 (在哪里查找类型的名称。) 可以覆盖 polymorphicKey。这允许每种类型都可以与键和名称的组合进行识别。没有定义如果有多个键在 SupportedType 中与有效名称同时存在会发生什么!只要组合是唯一的,就可以有多个具有相同键或名称的子类型。
polymorphicInfo 定义了类型名称及其子类型(它们不必是直接子类型)。无法检查这些类型是否真的是子类型,但如果使用 GenericPolymorphicInfo,这将进行检查,因此不会有问题。如果注册的子类型不是真正的子类型,则 StaticPolymorph 将忽略它(但不要依赖于这种行为)。当 StaticPolymorph 解决子类型时,它仅依赖于 polymorphicInfo 提供的信息,因此对于它来说,没有在输入类型(或在注册的子类型)中注册的子类型不存在。为了防止潜在的误用,禁止将没有覆盖 polymorphicInfo 的 Polymorphic 用作输入类型(如下例所示)。
Polymorphic 提供了一个 createPolymorphicInfo() 方法,该方法返回 GenericPolymorphicInfo。此方法具有可选参数 name,表示类型的多态名称(默认值是类型的真实名称)。GenericPolymorphicInfo 允许您使用 register() 和 with() 覆载注册子类型 (with() 返回 self 以允许链接)。
示例
class A: Polymorphic {
class var polymorphicKey: String {
return "K"
}
class var polymorphicInfo: PolymorphicInfo {
return createPolymorphicInfo(name: "Base").with(subtypes: B.self, D.self)
}
}
class B: A {
override class var polymorphicInfo: PolymorphicInfo {
return createPolymorphicInfo().with(subtype: C.self)
}
}
class C: B {
override class var polymorphicKey: String {
return "C"
}
}
class D: C {
override class var polymorphicInfo: PolymorphicInfo {
return createPolymorphicInfo()
}
}注意:此示例跳过了 Map 协议的实现。
有一些需要注意的事情。
C覆盖了polymorphicKey,这意味着:A和B的键是 "K",而C和D的键是 "C"。因此SupportedType.dictionary(["C": .string("C")]代表C,而SupportedType.dictionary(["K": .string("C")]在这种上下文中没有意义。A有显式的名称 "Base"。因此SupportedType.dictionary(["K": .string("Base")]代表A。C没有覆盖polymorphicInfo。这意味着不能将C用作输入类型(将引发异常),而尽管它永远不会解析为其他类型,但D可以使用。D在A中而不是在B中注册。因此,由于B不了解D,如果B是输入类型,则永远无法获取D作为子类型。A了解C,因为C在B中注册,而B在A中注册。
线程安全
DataMapper 旨在在后台线程中使用(默认实现是线程安全的)。如果您这样使用它,请确保 Map 协议中所有方法的实现都是线程安全的(或者您所使用的对象不能同时在两个线程中使用)。您自己实现的类似 Serializer、Polymorph、Transformation 等协议的实现在线程安全方面也必须保证。
版本控制
该库使用语义版本控制。在 1.0 版本之前,API 可能即使在次要版本中也可能出现破坏性更改。我们考虑 0.1 版本为预发布版本,这意味着 API 应该是稳定的,但在实际项目中尚未经过测试。在此测试之后,我们进行必要的调整,并将版本提升到 1.0(首次发布)。
作者
- Tadeas Kriz,[email protected]
- Filip Dolník,[email protected]
测试中使用的库
许可证
DataMapper 在 MIT 许可证 下可用。