HandyJSON 5.0.4-beta

HandyJSON 5.0.4-beta

测试已测试
语言语言 SwiftSwift
许可证 NOASSERTION
发布最后发布2021年7月
SPM支持 SPM

xuyecanH. Li维护。



HandyJSON 5.0.4-beta

  • 作者:
  • xuyecan

HandyJSON

为了处理 iOS 14 beta4 上的崩溃,请尝试版本 5.0.3-beta

HandyJSON 是一个 Swift 编写的框架,它使得在 iOS 上将模型对象(纯类/结构体)与 JSON 之间的转换变得更加容易。

与其他框架相比,HandyJSON 最显著的特征是它不要求对象继承自 NSObject(不使用 KVC 但使用反射),也不实现 'mapping' 函数(直接写入内存以实现属性赋值)。

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

内容

功能

  • 序列化/反序列化对象/将 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的支持,请查看swift2分支

CocoaPods

将以下行添加到您的Podfile

pod 'HandyJSON', '~> 5.0.2'

然后,运行以下命令

$ pod install

Carthage

您可以通过在Cartfile中添加以下行将HandyJSON作为依赖项

github "alibaba/HandyJSON" ~> 5.0.2

手动

您可以通过以下步骤将HandyJSON手动集成到项目中

  • 打开Terminalcd到您的顶级项目目录,并将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'支持由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可以通过实现可选的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/ImplicitUnwrappedOptional // T 是上述类型之一

  • 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() {}
}

在这种情况下,需要使用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() {}
}

信用

  • 反射:在第一次使用 Swift 镜像机制版本之后,HandyJSON 导入了反射库并重写了部分代码用于类属性检查。
  • ObjectMapper:为了使 HandyJSON 更符合通用风格,Mapper 函数支持 ObjectMapper 设计的 Transform。并且我们从 ObjectMapper 导入了一些测试用例。

许可

HandyJSON 在 Apache 许可证下发布,版本 2.0。有关详细信息,请参阅 LICENSE。