HandyJSON

为了解决 iOS 15 beta3 上的崩溃问题,请尝试 5.0.4-beta 版本

HandyJSON 是一个用 Swift 编写的框架,可以方便地在 iOS 上将模型对象(纯类/结构体)转换为 JSON 以及从 JSON 转换过来。

与其他框架相比,HandyJSON 最重要的特性是不需要对象继承自 NSObject(不使用 KVC 而是使用反射),也不需要实现“映射”函数(直接写入内存以实现属性赋值)。

HandyJSON 完全依赖于从 Swift 运行时代码推断出的内存布局规则。 我们正在密切关注它,并将跟踪它的每一个变化。

Build Status Carthage compatible Cocoapods Version Cocoapods Platform Codecov branch

中文文档

交流群

群号: 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

目录

特性

支持的类型概述可以在文件 BasicTypes.swift 中找到

要求

安装

要与 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 分支

Cocoapods

将以下行添加到您的 Podfile

pod 'HandyJSON', '~> 5.0.2'

然后,运行以下命令

$ pod install

Carthage

您可以通过将以下行添加到您的 Cartfile 来添加对 HandyJSON 的依赖

github "alibaba/HandyJSON" ~> 5.0.2

手动

您可以按照以下步骤手动将 HandyJSON 集成到您的项目中

git init && git submodule add https://github.com/alibaba/HandyJSON.git

您从哪个 Products 文件夹中选择并不重要,但您选择哪个 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)
}

Optional/ImplicitlyUnwrappedOptional/集合/...

“HandyJSON”支持由 optionalimplicitlyUnwrappedOptionalarraydictionaryobjective-c base typenested 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 允许您自定义键到 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)
}

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)

支持的属性类型

序列化

基础知识

现在,需要序列化为 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

映射和排除

一切都像我们在反序列化时所做的那样。 排除的属性既不会参与反序列化也不会参与序列化。 映射器项目定义了反序列化规则和序列化规则。 请参考上面的用法。

常见问题解答 (FAQ)

问:为什么映射函数在继承对象中不起作用?

答:由于某些原因,您应该在超类(如果有多层,则为根类)中定义一个空的映射函数,并在子类中覆盖它。

didFinishMapping 函数也是如此。

问:为什么我的 didSet/willSet 不起作用?

答:由于 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() {}
}

在这种情况下,NSObjectdynamic 都是必需的。

并且在自 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。