SGYSwiftJSON 2.0

SGYSwiftJSON 2.0

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最新发布2016年10月
SwiftSwift 版本3.0
SPM支持 SPM

Sean G. Young 维护。



  • 作者
  • Sean G. Young

SGYSwiftJSON

一个旨在提供将 Swift 类型自动转换为 JSON 及其从 JSON 转换回的一种类型安全方法的库。

摘要

SGYSwiftJSON 是一个旨在大大简化仅 Swift 模型的序列化和反序列化的库。该库的主要目标是消除将任意对象转换为 JSON 以及相反操作所需的大部分代码。这包括对集合、字典和复杂数字中包含的类型的递归转换。它提供了现成的功能支持大多数常见的 Foundation 类型以及复杂类型的可继承的基础类(非必需,但更简便)。它还提供了一系列协议,允许扩展到非寻常对象。

快速开始

大多数为 JSON 序列化编写的模型都已经得到了支持。任何符合以下条件的数据结构都应该可以即时工作

  • 所有集合都是 ArraySetNSArrayNSMutableArray 类型,其元素类型遵循这些规则集合。
  • 所有字典都有一个 StringNSString 键类型,其值类型遵循这些规则集合。
  • 所有复杂数据类型都遵循 JSONKeyValueCreatable。这可以通过使用 JSONCreatableObject 作为基类来实现。
  • 所有数字类型都是 NSNumberNSDecimalNumber(可能被标记为可选)或者可以桥接到 NSNumber(不能被标记为可选)。

如果您不希望遵守上述限制,则可以使用定义的协议扩展大多数其他类型。有关这些协议的详细信息以及它们在序列化和反序列化过程中的评估方法,请见下文。

注意:如果您不想实现反序列化,则上述限制中的许多都不适用或者大大放宽。

序列化

序列化通过协议和 Swift 的 Mirror 支持。任何传递给进行序列化的对象都会检查以下条件

  1. 符合 JSONProxyProvider 规范 - 对象中的 jsonProxy 属性会被检索并通过相同的逻辑树传递。
  2. 符合 JSONLeafRepresentable 规范 - 服从此协议的对象可以被表示为 JSON 叶子对象。例如,NSStringNSNumberNSNull。除了接受的 3 个叶子值外,结构体 StringBoolIntFloatDouble 符合此规范。
  3. Date 类型 - 如果对象是 Date 类型且不符合上述任何协议,则使用 dateConversionBlock(如果提供)来将其转换为 JSONLeafValue,或者如果没有提供块,则跳过属性。
  4. 符合 SGYDictionaryReflection 规范 - 对象将转换为键为字符串、值为对象的字典。通用的 DictionaryNSDictionary 都遵循此规范。
  5. 符合 SGYCollectionReflection 规范 - 对象包含的元素将被转换并放入数组中。数组、NSArraySet 都遵循此规范。
  6. 以上均不符合 - 对象的属性和值将使用 Mirror 进行枚举并转换为字典。

反序列化

反序列化比序列化更困难,因为它要求所有类型都有无参初始化器,可以分配任意值,并且能够报告它们包含的类型。在反序列化 NSArrayNSDictionaryJSONSerialization 产生的唯一对象)时,将执行以下逻辑

  1. 确定类型参数是根据反序列化对象所在的协议来确定的
    • 如果对象服从 JSONKeyValueCreatable 协议,则使用 Mirror 确定对象的属性和类型值。由 JSONSerialization 产生的对象必须是一个 NSDictionary,否则会抛出错误。
    • 如果对象服从 JSONDictionaryCreatable 协议,则使用该协议的 keyValueTypes 属性检索对象的键和值类型。由 JSONSerialization 产生的对象必须是一个 NSDictionary,否则会抛出错误。
    • 如果对象服从 JSONCollectionCreatable 协议,则使用该协议的 elementType 属性检索对象的元素类型。由 JSONSerialization 产生的对象必须是一个 NSArray,否则会抛出错误。
  2. 如果要将值反序列化为数组,则所有值都将转换为数组的 Element 类型。类似地,字典的值将转换为它们的 Value 类型。对于复杂对象,值将使用其 Mirror 属性表示进行转换。此转换使用以下逻辑进行
    1. 如果声明类型是 AnyAnyObject 或声明类型与反序列化类型匹配,则直接分配反序列化类型。
    2. 如果声明类型是 Date,则使用 dateConversionBlock 将反序列化的 Any 值转换为 Date。如果未声明块或块返回 nil,则不会分配属性。
    3. 如果反序列化的值是叶子值,则反序列化类型必须符合 JSONLeafConvertable 并使用叶子值构造并分配。否则不会分配反序列化值。
    4. 如果反序列化的值是 [Any] 类型且声明类型是 JSONCollectionCreatable,则使用数组的转换逻辑初始化一个数组并返回。否则不会分配反序列化值。
    5. 如果反序列化的值是 [String: Any] 类型且声明类型是 JSONDictionaryCreatable,则使用字典的转换逻辑初始化一个数组并返回。否则不会分配反序列化值。

示例

所有示例都将基于一个任意的 Person 类。我们将从一个非常基本的类开始,并通过修改/扩展它来利用更复杂/Swifty 的属性。

基本示例

让我们从一个无需转换的模型开始。

class Person {
    var name: String?
    var birthdate: String?
    var favoriteColor: String?
    var bestFriends: [Person]?
    var categorizedFriends: [String: Person]?
    var followersCount: NSNumber?
}

序列化很简单。假设 someGuy 是具有任意值的 Person 类的实例。

let serializer = SGYJSONSerializer()
do {
    let jsonData = try serializer.serialize(someGuy)
} catch let error as NSError {
    // Optionally catch specific errors
}

反序列化需要一个小改动。我们需要一种方法来分配类属性。最简单的方法是继承自实现了 JSONKeyValueCreatableJSONCreatableObject,它是通过 NSObject 的键值编码实现的。

class Person: JSONCreatableObject

然后进行反序列化。

let deserializer = SGYJSONDeserializer()
do {
    let jsonGuy: Person = try deserializer.deserialize(jsonData)
} catch let error as NSError {
    // Optionally catch specific errors
}

更Swifty示例

上述示例已经比正常转换要简多了。但我们仍在使用那个ObjC对象 NSNumber,而展开它很烦人。幸运的是,对于序列化,Swift的自动桥接为我们完成了工作。我们可以简单地重新定义 followersCount 为:

    var followersCount: Int?

这将顺利地进行序列化。但是反序列化是一个问题。属性 followersCount 将被正确地转换为 Int。但是 JSONCreatableObject 使用键值编码,尝试分配给 Int? 会抛出错误。解决方案很简单

    var followersCount: Int = 0 // No reason to be nil anyway.  If value doesn't exist 0 is reasonable.

这将顺利地进行反序列化。如果你坚定地想使用可选的Foundation类型,请参阅下一节关于重写 setValue:forProperty 的信息。

更Swift示例

现在模型看起来更接近于不考虑反序列化就能设计的东西。但是,关于 favoriteColor 属性呢?它绝对适用于成为Swift枚举。序列化同样简单。我们定义一个符合复合协议的 Color 枚举:

enum Color: String, JSONLeafEnum {
    case red = "Red", green = "Green", blue = "Blue", yellow = "Yellow"
}

现在修改 Person 类型。

    var favoriteColor: Color?

这将顺利地进行序列化并产生关联的 rawValue 作为JSON值。反序列化比 Int 更困难,原因相同。但是Swift枚举根本无法通过KVC分配。唯一的选项是重写 setValue:property:

    override func setValue(value: Any, property: String) throws {
        if property == "color" { color = value as? Color }
        else { try super.setValue(value, property: property) }
    }

现在 Person 类将把JSON值反序列化为可选的Swift枚举。

日期

严格来说,日期不是JSON值。但是它的使用和转换需求是恒定的。因为有太多方式可以表示日期值,序列化和反序列化类都提供了一个用于转换的 dateConversionBlock 属性。首先,将 birthdate 转换为 Date 类型。

    var birthdate: Date?

由于大多数日期的JSON表示都是字符串或数字类型,因此 SGYJSONSerializer 上的日期转换块预期返回一个 JSONLeafValue 枚举。假设我们的JSON消费者预期日期为 DateFormatterMediumStyle,那么与之前的序列化相比,唯一增加的是分配此块。

let formatter = DateFormatter()
formatter.dateStyle = .MediumStyle
serializer.dateConversionBlock = { (date) in formatter.stringFromDate(date) }
// Continue serialization as before

反序列化类似。主要区别在于反序列化器上的 dateConversionBlock 接受一个更通用的 Any 参数,以便将任意反序列化的值转换为 Date

let formatter = DateFormatter()
formatter.dateStyle = .mediumStyle
deserializer.dateConversionBlock = { (jsonValue) -> Date? in
    guard let value = jsonValue as? String else { return nil }
    return formatter.dateFromString(value)
}
// Continue deserialization as before