swift 处理 JSON - 转换 JSON 和 Model

背景

  很多时候我们再服务端请求下来的数据都是JSON格式,我们需要将这些数据展示在UI界面。我们一般都会先将JSON转化为数据模型或字典进行使用。
  为了更快的开发我们时常会用到一些开源库,其中JSON解析的库必不可少,在OC开发中我们常用的JSON解析库如MJExtensionJSONModel等,这些库基本都是利用runtime实现读取属性并利用kvc赋值的。在swift中由于runtime的局限性,产生了一些通过反射等机制实现的库,比如SwiftyJSONObjectMapperHandyJSON等,而 HandyJSON 是其中使用最舒服的一个库,本文将介绍用 HandyJSON 来进行Model和JSON间的互相转换。

简单示例

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
}

序列化

1
2
3
4
5
6
7
8
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

文档目录

特性

  • 序列化Model到JSON、从JSON反序列化到Model

  • 自然地以Model的属性名称作为解析JSON的Key,不需要额外指定

  • 支持Swift中大部分类型

  • 支持class、struct定义的Model

  • 支持自定义解析规则

  • 类型自适应,如JSON中是一个Int,但对应Model是String字段,会自动完成转化

具体支持的类型,可以参考代码文件: BasicTypes

环境要求

  • iOS 8.0+/OSX 10.9+/watchOS 2.0+/tvOS 9.0+

  • Swift 2.3+ / Swift 3.0+

安装

HandyJSON只在Swift3.x版本上(master分支)开发新特性,在Swift2.x中使用,参见: swift2 branch

具体操作指引参考 英文版READMEInstallation 章节。

反序列化

基本类型

要支持从JSON串反序列化,Model定义时要声明服从HandyJSON协议。确实是一个协议,而不是继承自NSObject

服从HandyJSON协议,需要实现一个空的init方法。

1
2
3
4
5
6
7
8
9
10
11
12
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

对于声明为struct的Model,由于struct默认提供了空的init方法,所以不需要额外声明。

1
2
3
4
5
6
7
8
9
10
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) {
// …
}

但需要注意,如果你为struct指定了别的构造函数,那就要显示声明一个空的init函数。

支持enum

支持值类型的enum,且需要声明服从HandyJSONEnum协议。不再需要其他特殊处理了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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支持这些非基础类型,包括嵌套结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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支持指定从哪个具体路径开始解析,反序列化到Model。

1
2
3
4
5
6
7
8
9
10
11
12
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)
}

组合对象

注意,如果Model的属性不是基本类型或集合类型,那么它必须是一个服从HandyJSON协议的类型。

如果是泛型集合类型,那么要求泛型实参是基本类型或者服从HandyJSON协议的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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的第一层表达的是数组,可以转化它到一个Model数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
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支持自定义映射关系,或者自定义解析过程。你需要实现一个可选的mapping函数,在里边实现NSString值(HandyJSON会把对应的JSON字段转换为NSString)转换为你需要的字段类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Cat: HandyJSON {
var id: Int64!
var name: String!
var parent: (String, 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
})
}
}

let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\"}"

if let cat = Cat.deserialize(from: jsonString) {
print(cat.id)
print(cat.parent)
}

排除指定属性

如果在Model中存在因为某些原因不能实现HandyJSON协议的非基本字段,或者不能实现HandyJSONEnum协议的枚举字段,又或者说不希望反序列化影响某个字段,可以在mapping函数中将它排除。如果不这么做,可能会出现未定义的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
}

支持的属性类型

  • Int/Bool/Double/Float/String/NSNumber/NSString

  • NSArray/NSDictionary

  • Int8/Int16/Int32/Int64/UInt8/UInt16/UInt23/UInt64

  • Optional<T>/ImplicitUnwrappedOptional<T> // T is one of the above types

  • Array<T> // T is one of the above types

  • Dictionary<String, T> // T is one of the above types

  • 以上类型的嵌套

序列化

基本类型

现在,序列化也要求Model声明服从HandyJSON协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

自定义映射和排除

和反序列化一样,只要定义mappingexclude就可以了。被排除的属性,序列化和反序列化都不再影响到它。而在mapping中定义的Transformer,同时定义了序列化和反序列的规则,所以只要为属性指明一个Transformer关系就可以了。

附加

提供几个 JSON 字符串生成 model 类的工具: