为了解决 iOS 15 beta3 上的崩溃问题,请尝试 5.0.4-beta 版本
HandyJSON 是一个用 Swift 编写的框架,可以方便地在 iOS 上将模型对象(纯类/结构体)转换为 JSON 以及从 JSON 转换过来。
与其他框架相比,HandyJSON 最重要的特性是不需要对象继承自 NSObject(不使用 KVC 而是使用反射),也不需要实现“映射”函数(直接写入内存以实现属性赋值)。
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
对于旧版 Swift 2.x 的支持,请查看 swift2 分支。
将以下行添加到您的 Podfile
pod 'HandyJSON', '~> 5.0.2'
然后,运行以下命令
$ pod install
您可以通过将以下行添加到您的 Cartfile
来添加对 HandyJSON
的依赖
github "alibaba/HandyJSON" ~> 5.0.2
您可以按照以下步骤手动将 HandyJSON
集成到您的项目中
终端
,cd
进入您的顶层项目目录,并将 HandyJSON
添加为子模块git init && git submodule add https://github.com/alibaba/HandyJSON.git
打开新的 HandyJSON
文件夹,将 HandyJSON.xcodeproj
拖到您项目的 项目导航器
中。
在 项目导航器
中选择您的应用程序项目,然后在右侧窗口中打开 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 文本的第一层是一个数组,我们将它转换为对象数组。
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
允许您自定义键到 JSON 字段的映射,或任何属性的解析方法。 您需要做的就是实现一个可选的 mapping
函数,并在其中执行操作。
我们从 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)
}
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<T>
// T 是上述类型之一
Array<T>
// T 是上述类型之一
Dictionary<String, T>
// 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
一切都像我们在反序列化时所做的那样。 排除的属性既不会参与反序列化也不会参与序列化。 映射器项目定义了反序列化规则和序列化规则。 请参考上面的用法。
答:由于某些原因,您应该在超类(如果有多层,则为根类)中定义一个空的映射函数,并在子类中覆盖它。
didFinishMapping
函数也是如此。
答:由于 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() {}
}
HandyJSON 在 Apache License, Version 2.0 下发布。 有关详细信息,请参阅 LICENSE。