HandyJSON
为了处理 iOS 14 beta4 上的崩溃,请尝试版本 5.0.3-beta
HandyJSON 是一个 Swift 编写的框架,它使得在 iOS 上将模型对象(纯类/结构体)与 JSON 之间的转换变得更加容易。
与其他框架相比,HandyJSON 最显著的特征是它不要求对象继承自 NSObject(不使用 KVC 但使用反射),也不实现 'mapping' 函数(直接写入内存以实现属性赋值)。
HandyJSON 完全依赖于从 Swift 运行时代码推断出的内存布局规则。我们正在关注它,并将随时支持任何变化。
中文文档
交流群
群号: 581331250
示例代码
反序列化
class BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
required init() {}
}
let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}"
if let object = BasicTypes.deserialize(from: jsonString) {
print(object.int)
print(object.doubleOptional!)
print(object.stringImplicitlyUnwrapped)
}
序列化
let object = BasicTypes()
object.int = 1
object.doubleOptional = 1.1
object.stringImplicitlyUnwrapped = “hello"
print(object.toJSON()!) // serialize to dictionary
print(object.toJSONString()!) // serialize to JSON string
print(object.toJSONString(prettyPrint: true)!) // serialize to pretty JSON string
内容
功能
-
序列化/反序列化对象/将 JSON 序列化为对象/从对象中反序列化 JSON
-
自然使用对象属性名进行映射,无需指定映射关系
-
支持几乎所有 Swift 类型,包括枚举
-
支持结构体
-
自定义转换
-
类型适配,例如字符串 json 字段映射到 int 属性,int json 字段映射到字符串属性
支持的类型概述请参阅文件:BasicTypes.swift
要求
-
iOS 8.0+/OSX 10.9+/watchOS 2.0+/tvOS 9.0+
-
Swift 3.0+ / Swift 4.0+ / Swift 5.0+
安装
与Swift 5.0/5.1 (Xcode 10.2+/11.0+)配合使用,版本 == 5.0.2
与Swift 4.2 (Xcode 10)配合使用,版本 == 4.2.0
与Swift 4.0配合使用,版本 >= 4.1.1
与Swift 3.x配合使用,版本 >= 1.8.0
对旧版Swift2.x的支持,请查看
CocoaPods
将以下行添加到您的Podfile
pod 'HandyJSON', '~> 5.0.2'
然后,运行以下命令
$ pod install
Carthage
您可以通过在Cartfile
中添加以下行将HandyJSON
作为依赖项
github "alibaba/HandyJSON" ~> 5.0.2
手动
您可以通过以下步骤将HandyJSON
手动集成到项目中
- 打开
Terminal
,cd
到您的顶级项目目录,并将HandyJSON
添加为子模块
git init && git submodule add https://github.com/alibaba/HandyJSON.git
-
打开新的
HandyJSON
文件夹,将HandyJSON.xcodeproj
拖到您项目的Project Navigator
中。 -
在
Project Navigator
中选择您的应用程序项目,在右窗格中打开General
面板。 -
在
Embedded Binaries
部分下单击+`按钮。
-
您将看到两个不同的
HandyJSON.xcodeproj
文件夹,每个文件夹中都有一个包含四个不同版本HandyJSON.framework的Products文件夹。
您选择的Products文件夹无所谓,但您选择的HandyJSON.framework必须与您应用程序运行的平台匹配。
-
选择与您的应用程序应运行的平台匹配的四个
HandyJSON.framework
之一。 -
恭喜!
反序列化
基础知识
为了支持从JSON的反序列化,类/结构体需要遵循'HandyJSON'协议。这确实是协议,不是从NSObject继承的某个类。
为了遵循'HandyJSON',类需要实现一个空初始化器。
class BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
required init() {}
}
let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}"
if let object = BasicTypes.deserialize(from: jsonString) {
// …
}
支持结构体
对于结构体,因为编译器提供了默认的空初始化器,我们可以免费使用。
struct BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
}
let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}"
if let object = BasicTypes.deserialize(from: jsonString) {
// …
}
但也要注意,如果你在一个结构体中有指定的初始化器来覆盖默认的一个,你应该显式地声明一个空的(不需要required
修饰符)。
支持枚举属性
为了可转换,一个enum
必须遵循HandyJSONEnum
协议。现在不需要做特别的事情。
enum AnimalType: String, HandyJSONEnum {
case Cat = "cat"
case Dog = "dog"
case Bird = "bird"
}
struct Animal: HandyJSON {
var name: String?
var type: AnimalType?
}
let jsonString = "{\"type\":\"cat\",\"name\":\"Tom\"}"
if let animal = Animal.deserialize(from: jsonString) {
print(animal.type?.rawValue)
}
可选/隐式解包可选/集合/...
'HandyJSON'支持由optional
、implicitlyUnwrappedOptional
、array
、dictionary
、objective-c base type
、nested type
等属性组成的类/结构体。
class BasicTypes: HandyJSON {
var bool: Bool = true
var intOptional: Int?
var doubleImplicitlyUnwrapped: Double!
var anyObjectOptional: Any?
var arrayInt: Array<Int> = []
var arrayStringOptional: Array<String>?
var setInt: Set<Int>?
var dictAnyObject: Dictionary<String, Any> = [:]
var nsNumber = 2
var nsString: NSString?
required init() {}
}
let object = BasicTypes()
object.intOptional = 1
object.doubleImplicitlyUnwrapped = 1.1
object.anyObjectOptional = "StringValue"
object.arrayInt = [1, 2]
object.arrayStringOptional = ["a", "b"]
object.setInt = [1, 2]
object.dictAnyObject = ["key1": 1, "key2": "stringValue"]
object.nsNumber = 2
object.nsString = "nsStringValue"
let jsonString = object.toJSONString()!
if let object = BasicTypes.deserialize(from: jsonString) {
// ...
}
指定路径
HandyJSON
支持从JSON的指定路径进行反序列化。
class Cat: HandyJSON {
var id: Int64!
var name: String!
required init() {}
}
let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}"
if let cat = Cat.deserialize(from: jsonString, designatedPath: "data.cat") {
print(cat.name)
}
组合对象
请注意,所有需要反序列化的类/结构体的属性类型都需要符合HandyJSON
规范。
class Component: HandyJSON {
var aInt: Int?
var aString: String?
required init() {}
}
class Composition: HandyJSON {
var aInt: Int?
var comp1: Component?
var comp2: Component?
required init() {}
}
let jsonString = "{\"num\":12345,\"comp1\":{\"aInt\":1,\"aString\":\"aaaaa\"},\"comp2\":{\"aInt\":2,\"aString\":\"bbbbb\"}}"
if let composition = Composition.deserialize(from: jsonString) {
print(composition)
}
继承对象
需要反序列化的子类,其父类需要符合HandyJSON
规范。
class Animal: HandyJSON {
var id: Int?
var color: String?
required init() {}
}
class Cat: Animal {
var name: String?
required init() {}
}
let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}"
if let cat = Cat.deserialize(from: jsonString) {
print(cat)
}
JSON数组
如果一个JSON文本的第一级是数组,我们将它转换为对象数组。
class Cat: HandyJSON {
var name: String?
var id: String?
required init() {}
}
let jsonArrayString: String? = "[{\"name\":\"Bob\",\"id\":\"1\"}, {\"name\":\"Lily\",\"id\":\"2\"}, {\"name\":\"Lucy\",\"id\":\"3\"}]"
if let cats = [Cat].deserialize(from: jsonArrayString) {
cats.forEach({ (cat) in
// ...
})
}
从字典映射
HandyJSON
支持将Swift字典映射到模型。
var dict = [String: Any]()
dict["doubleOptional"] = 1.1
dict["stringImplicitlyUnwrapped"] = "hello"
dict["int"] = 1
if let object = BasicTypes.deserialize(from: dict) {
// ...
}
自定义映射
HandyJSON
可以通过实现可选的mapping
函数来自定义键映射到JSON字段,或者任何属性的解析方法。您所需要做的只是在该函数内执行相关操作。
我们从ObjectMapper
引入了转换器。如果您熟悉它,这里的用法几乎相同。
class Cat: HandyJSON {
var id: Int64!
var name: String!
var parent: (String, String)?
var friendName: String?
required init() {}
func mapping(mapper: HelpingMapper) {
// specify 'cat_id' field in json map to 'id' property in object
mapper <<<
self.id <-- "cat_id"
// specify 'parent' field in json parse as following to 'parent' property in object
mapper <<<
self.parent <-- TransformOf<(String, String), String>(fromJSON: { (rawString) -> (String, String)? in
if let parentNames = rawString?.characters.split(separator: "/").map(String.init) {
return (parentNames[0], parentNames[1])
}
return nil
}, toJSON: { (tuple) -> String? in
if let _tuple = tuple {
return "\(_tuple.0)/\(_tuple.1)"
}
return nil
})
// specify 'friend.name' path field in json map to 'friendName' property
mapper <<<
self.friendName <-- "friend.name"
}
}
let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\",\"friend\":{\"id\":54321,\"name\":\"Lily\"}}"
if let cat = Cat.deserialize(from: jsonString) {
print(cat.id)
print(cat.parent)
print(cat.friendName)
}
Date/Data/URL/Decimal/Color
HandyJSON
为一些非基本类型准备了一些有用的转换器。
class ExtendType: HandyJSON {
var date: Date?
var decimal: NSDecimalNumber?
var url: URL?
var data: Data?
var color: UIColor?
func mapping(mapper: HelpingMapper) {
mapper <<<
date <-- CustomDateFormatTransform(formatString: "yyyy-MM-dd")
mapper <<<
decimal <-- NSDecimalNumberTransform()
mapper <<<
url <-- URLTransform(shouldEncodeURLString: false)
mapper <<<
data <-- DataTransform()
mapper <<<
color <-- HexColorTransform()
}
public required init() {}
}
let object = ExtendType()
object.date = Date()
object.decimal = NSDecimalNumber(string: "1.23423414371298437124391243")
object.url = URL(string: "https://www.aliyun.com")
object.data = Data(base64Encoded: "aGVsbG8sIHdvcmxkIQ==")
object.color = UIColor.blue
print(object.toJSONString()!)
// it prints:
// {"date":"2017-09-11","decimal":"1.23423414371298437124391243","url":"https:\/\/www.aliyun.com","data":"aGVsbG8sIHdvcmxkIQ==","color":"0000FF"}
let mappedObject = ExtendType.deserialize(from: object.toJSONString()!)!
print(mappedObject.date)
...
排除属性
如果一个类/结构体的非基本属性无法符合 HandyJSON
/HandyJSONEnum
或者你只想不使用它进行反序列化,你应该在映射函数中排除它。
class NotHandyJSONType {
var dummy: String?
}
class Cat: HandyJSON {
var id: Int64!
var name: String!
var notHandyJSONTypeProperty: NotHandyJSONType?
var basicTypeButNotWantedProperty: String?
required init() {}
func mapping(mapper: HelpingMapper) {
mapper >>> self.notHandyJSONTypeProperty
mapper >>> self.basicTypeButNotWantedProperty
}
}
let jsonString = "{\"name\":\"cat\",\"id\":\"12345\"}"
if let cat = Cat.deserialize(from: jsonString) {
print(cat)
}
更新现有模型
HandyJSON
支持使用给定的 JSON 字符串或字典更新现有模型。
class BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
required init() {}
}
var object = BasicTypes()
object.int = 1
object.doubleOptional = 1.1
let jsonString = "{\"doubleOptional\":2.2}"
JSONDeserializer.update(object: &object, from: jsonString)
print(object.int)
print(object.doubleOptional)
支持的属性类型
-
Int
/Bool
/Double
/Float
/String
/NSNumber
/NSString
-
RawRepresentable
枚举 -
NSArray/NSDictionary
-
Int8/Int16/Int32/Int64
/UInt8/UInt16/UInt23/UInt64
-
Optional
// T 是上述类型之一/ImplicitUnwrappedOptional -
Array
// T 是上述类型之一 -
Dictionary
// T 是上述类型之一 -
上述类型的嵌套
序列化
基础知识
现在,需要序列化为 JSON 的类/模型也应该符合 HandyJSON
协议。
class BasicTypes: HandyJSON {
var int: Int = 2
var doubleOptional: Double?
var stringImplicitlyUnwrapped: String!
required init() {}
}
let object = BasicTypes()
object.int = 1
object.doubleOptional = 1.1
object.stringImplicitlyUnwrapped = “hello"
print(object.toJSON()!) // serialize to dictionary
print(object.toJSONString()!) // serialize to JSON string
print(object.toJSONString(prettyPrint: true)!) // serialize to pretty JSON string
映射和排除
这与我们所做的反序列化过程类似。被排除的属性既不参与反序列化也不参与序列化。映射项定义了反序列化和序列化的规则。请参阅上述用法。
常见问题解答
问题:为什么继承对象中的映射函数不起作用?
A: 由于某种原因,你应在父类(如果有多层,则为基础类)中定义一个空的映射函数,并在子类中重写它。
这与didFinishMapping
函数相同。
问题:为什么我的didSet/willSet不起作用?
A: 由于HandyJSON
直接将值写入内存以分配属性,它不会触发任何观察函数。您需要在反序列化后/之前显式调用didSet/willSet
逻辑。
但在版本1.8.0
之后,HandyJSON
通过KVC
机制处理动态属性,这会触发KVO
。这意味着,如果您确实需要didSet/willSet
,您可以像以下方式定义模型:
class BasicTypes: NSObject, HandyJSON {
dynamic var int: Int = 0 {
didSet {
print("oldValue: ", oldValue)
}
willSet {
print("newValue: ", newValue)
}
}
public override required init() {}
}
在这种情况下,需要使用NSObject
和dynamic
。
自版本1.8.0
以来,HandyJSON
提供了一个didFinishMapping
函数,允许您填充一些观察逻辑。
class BasicTypes: HandyJSON {
var int: Int?
required init() {}
func didFinishMapping() {
print("you can fill some observing logic here")
}
}
这可能有所帮助。
问题:如何支持枚举属性?
如果你的枚举符合RawRepresentable
协议,请查看支持枚举属性。或者使用EnumTransform
。
enum EnumType: String {
case type1, type2
}
class BasicTypes: HandyJSON {
var type: EnumType?
func mapping(mapper: HelpingMapper) {
mapper <<<
type <-- EnumTransform()
}
required init() {}
}
let object = BasicTypes()
object.type = EnumType.type2
print(object.toJSONString()!)
let mappedObject = BasicTypes.deserialize(from: object.toJSONString()!)!
print(mappedObject.type)
否则,您应实现自己的自定义映射函数。
enum EnumType {
case type1, type2
}
class BasicTypes: HandyJSON {
var type: EnumType?
func mapping(mapper: HelpingMapper) {
mapper <<<
type <-- TransformOf<EnumType, String>(fromJSON: { (rawString) -> EnumType? in
if let _str = rawString {
switch (_str) {
case "type1":
return EnumType.type1
case "type2":
return EnumType.type2
default:
return nil
}
}
return nil
}, toJSON: { (enumType) -> String? in
if let _type = enumType {
switch (_type) {
case EnumType.type1:
return "type1"
case EnumType.type2:
return "type2"
}
}
return nil
})
}
required init() {}
}
信用
- 反射:在第一次使用 Swift 镜像机制版本之后,HandyJSON 导入了反射库并重写了部分代码用于类属性检查。
- ObjectMapper:为了使 HandyJSON 更符合通用风格,Mapper 函数支持 ObjectMapper 设计的 Transform。并且我们从 ObjectMapper 导入了一些测试用例。
许可
HandyJSON 在 Apache 许可证下发布,版本 2.0。有关详细信息,请参阅 LICENSE。