-
Notifications
You must be signed in to change notification settings - Fork 7
Home
这是一个可以可以让你事半功倍的工具,他可以将所有JSON操作都集成在一起。
我们都知道,JSON结果总是(或是有时)会导致应用崩溃,这是因为我们无从获知从服务器获取的JSON数据是否完全正确。 现在,Swift 带给了我们强类型以及类型安全。 So... 为什么我们仍然坚持使用Objective-C和其它JSON库呢?
-
添加
PPJSONSerialization.swift
到你的工程 (我并不打算发布到CocoaPods,这只是一个文件而已。) -
创建你的自定义类并继承
PPJSONSerialization
class Artist: PPJSONSerialization {
var name: String?
var height: Double = 0.0
}
- 从网络获取数据并使用你刚才创建的类生成一个实例
let mockString = "{\"name\": \"Pony Cui\", \"height\": 164.0}"
let artistData = Artist(JSONString: mockString)
- 现在,你可以自信的使用数据了
if let artistData = Artist(JSONString: mockString) {
print(artistData.name)
print(artistData.height)
}
还记得我们将 height
在 Artist
类中定义为 Double
吗?
如果JSON数据中的类型是错误的,大部分解析库都会直接丢弃这个结果,又或者抛出一个错误信息。
但是 PPJSONSerialization 会尽力将错误的结果转换成 Double
例子
let mockString = "{\"name\": \"Pony Cui\", \"height\": \"164.0\"}" //height is a string value
if let artistData = Artist(JSONString: mockString) {
print(artistData.name)
print(artistData.height) // Now it convert to Double, print 164.0
}
类型转换可以应用在 string, double, int, bool 上
Swift 最迷人的地方在于 Optional, PPJSONSerialization 是支持这一特性的。
class Artist: PPJSONSerialization {
var name: String? // We define it as a optional value
var height: Double = 0.0
}
let mockString = "{\"height\": \"164.0\"}"
if let artistData = Artist(JSONString: mockString) {
if let name = artistData.name {
print(name) // 这里的代码不会执行,因为数据中并没有name字段
}
}
永远不用担心这两个问题, PPJSONSerialization的初始化是可能返回nil的,这就给予了你一个可能控制的机会。 同时 Null 值永远不会被赋值到实例中。
Swift 数组总是要求开发者定义其内容的类型, 你不能存储其它类型在里面。 但是, JSON是可以的, 这在Objective-C时代,是一件棘手的事情。
PPJSONSerialization 可以轻松地完成这件事情。
例子, 你定义了一个属性 friends
, 它的成员都是 String
类型.
class Artist: PPJSONSerialization {
var name: String?
var height: Double = 0.0
var friends = [String]()
}
使用方法就像之前一样,请注意,这里我故意添加了三个非法类型,但是 PPJSONSerialization 会转换它。
let mockString = "{\"friends\": [\"Jack\", \"Leros\", \"Max\", \"LGY\", 1, 2, 3]}"
if let artistData = Artist(JSONString: mockString) {
for friend in artistData.friends {
print(friend)
}
}
/*
Prints:
Jack
Leros
Max
LGY
1
2
3
*/
字典也同样支持泛型,但是我强烈推荐你使用子类型去处理字典。
class Artist: PPJSONSerialization {
var name: String?
var height: Double = 0.0
var friendsHeight = [String: Double]() // now we change it as dictionary
}
let mockString = "{\"friendsHeight\": {\"Jack\": 170, \"Leros\": 180, \"Max\": 168, \"LGY\": 177}}"
if let artistData = Artist(JSONString: mockString) {
for (friend, height) in artistData.friendsHeight {
print("\(friend), height:\(height)")
}
}
/*
Prints:
Leros, height:180.0
Max, height:168.0
LGY, height:177.0
Jack, height:170.0
*/
一种常见的情况是,字典里面包含另一个字典,当然,你可以使用泛型去处理。但是,当你正在追求一个面向对象强迫症的妹子的时候,子类型或许是更好的选择。
提示: 子类型可以应用在 Dictionary/Array/Property
class Artist: PPJSONSerialization {
var name: String?
var height: Double = 0.0
var friends = [ArtistFriend]()
}
// 子类型也需要继承 PPJSONSerialization
class ArtistFriend: PPJSONSerialization {
var name: String?
var height: Double = 0.0
}
let mockString = "{\"friends\": [{\"name\": \"Jack\", \"height\": \"177.0\"}, {\"name\": \"Max\", \"height\": \"188.0\"}]}"
if let artistData = Artist(JSONString: mockString) {
for friend in artistData.friends {
print("\(friend.name), height:\(friend.height)")
}
}
/*
Prints:
Optional("Jack"), height:177.0
Optional("Max"), height:188.0
*/
如是你希望控制类型转换的过程,只需要为指定的类实现PPCoding协议就可以了。
例如,我们希望将时间戳(Int)转换为NSDate。
class CodeingStruct: PPJSONSerialization {
var codeingDate: NSDate = NSDate() // 定义一个属性,并将其设为 NSDate,初始化它。
}
extension NSDate: PPCoding {
func encodeAsPPObject() -> AnyObject? {
return timeIntervalSince1970
}
func decodeWithPPObject(PPObject: AnyObject) -> AnyObject? {
if let timestamp = PPObject as? NSTimeInterval {
return NSDate(timeIntervalSince1970: timestamp) // 在这里我们扩展 NSDate
}
return nil
}
}
let codingDateJSON = "{\"codeingDate\":1444885037}"
if let test = CodeingStruct(JSONString: codingDateJSON) {
XCTAssert(test.codeingDate.description == "2015-10-15 04:57:17 +0000", "Pass")
}
- PPCoding 同时支持序列化操作
- 不支持序列化 Optional 值
如果最顶级的JSON结构是数组,你就需要继承PPJSONArraySerialization
了,然后定义一个属性root
,指定它的容器类型。
class ArrayStruct: PPJSONArraySerialization {
var root = [Int]()
}
let arrayJSON = "[1,0,2,4]"
if let arrayObject = ArrayStruct(JSONString: arrayJSON) {
XCTAssert(arrayObject.root.count == 4, "Pass")
XCTAssert(arrayObject.root[0] == 1, "Pass")
XCTAssert(arrayObject.root[1] == 0, "Pass")
XCTAssert(arrayObject.root[2] == 2, "Pass")
XCTAssert(arrayObject.root[3] == 4, "Pass")
}
如果数组有两个维度以上, 按照下面的方法处理。
class MultipleDimensionArray: PPJSONSerialization {
var twoDimension = [[Int]]()
var threeDimension = [[[Int]]]()
}
let twoDimensionArrayJSON = "{\"twoDimension\": [[1,0,2,4], [1,0,2,4]]}"
if let test = MultipleDimensionArray(JSONString: twoDimensionArrayJSON) {
XCTAssert(test.twoDimension[0][0] == 1, "Pass")
XCTAssert(test.twoDimension[0][1] == 0, "Pass")
XCTAssert(test.twoDimension[0][2] == 2, "Pass")
XCTAssert(test.twoDimension[0][3] == 4, "Pass")
XCTAssert(test.twoDimension[1][0] == 1, "Pass")
XCTAssert(test.twoDimension[1][1] == 0, "Pass")
XCTAssert(test.twoDimension[1][2] == 2, "Pass")
XCTAssert(test.twoDimension[1][3] == 4, "Pass")
}
有时候,从网络上取回来的数据,它的键名并非和应用中的一致,有一种很方便的方法可以处理这种情况。 改写 mapping
方法,返回一个字典,包含 ["JSON键名": "属性键名"]
就可以了
class Artist: PPJSONSerialization {
var name: String = ""
var height: Double = 0.0
override func mapping() -> [String : String] {
return ["xxxname": "name"]
}
}
let mockString = "{\"xxxname\": \"Pony Cui\"}"
if let artistData = Artist(JSONString: mockString) {
print(artistData.name)
}
你可以序列化方化将PPJSONSerialization类转换为JSON字符串或JSON数据。
class Artist: PPJSONSerialization {
var name: String = "" // Note that, optional value is not serialize to string always!
var height: Double = 0.0
}
let artistData = Artist()
artistData.name = "Pony Cui"
artistData.height = 164.0
let string = artistData.JSONString()
print(string)
/*
Prints: {"name":"Pony Cui","height":164}
*/
你可能会为一些类定义一个命名空间, 这在Swift中是一个良好的习惯。
这是一个例子,我将所有的 PPJSONSerialization 类都放在 DataModel 命名空间内。
class DataModel { // Struct 和 Enum 也是可以的
class Artist: PPJSONSerialization {
var name: String?
var mainSong = Song() // 使用命名空间,你必须初始化这个属性,并且不能使用Optional值。
var songs = [Song()] // 必须为数组添加至少一个初始化值。
var mapSong = [".": Song()] // 必须为字典添加至少一个初始化值,其键可以是任意String。
}
class Song: PPJSONSerialization {
var name: String?
var duration: Double = 0.0
}
}
let JSONString = "{\"name\": \"Pony Cui\", \"mainSong\": {\"name\":\"Love Song\", \"duration\": 168.0}, \"songs\": [{\"name\":\"Love Song\", \"duration\": 168.0}], \"mapSong\": {\"sampleKey\": {\"name\":\"Love Song\", \"duration\": 168.0}}}"
if let test = DataModel.Artist(JSONString: JSONString) {
XCTAssert(test.name! == "Pony Cui", "Pass")
XCTAssert(test.mainSong.name == "Love Song", "Pass")
XCTAssert(test.mainSong.duration == 168.0, "Pass")
XCTAssert(test.songs[0].name == "Love Song", "Pass")
XCTAssert(test.songs[0].duration == 168.0, "Pass")
XCTAssert(test.mapSong["sampleKey"]?.name == "Love Song", "Pass")
XCTAssert(test.mapSong["sampleKey"]?.duration == 168.0, "Pass")
}
else {
XCTAssert(false, "Failed")
}
- 你可以将设定多个层级的命名空间,这是完全没有问题的。
PPJSONSerialization 仍然在开发中,欢迎提供BUG线索或者提出使用上的疑问,我会在一小时内回复。
- iOS 7.0+ / Mac OS X 10.10+
- Xcode 7.0
- Swift 2.0
Add PPJSONSerialization.swift
into your project, that's enough
MIT License, Please feel free to use it.
- Thanks for @onevcat suggest use Failable init.
- Thanks for @neil-wu using and reported issues.