Skip to content
PonyCui edited this page Oct 16, 2015 · 9 revisions

PPJSONSerialization

介绍

这是一个可以可以让你事半功倍的工具,他可以将所有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)
}

功能

类型转换

还记得我们将 heightArtist 类中定义为 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 上

Optional (不稳定)

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字段
    }
}

Null 崩溃 和 数据异常

永远不用担心这两个问题, 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

如是你希望控制类型转换的过程,只需要为指定的类实现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

如果最顶级的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")
}

Key Mapping

有时候,从网络上取回来的数据,它的键名并非和应用中的一致,有一种很方便的方法可以处理这种情况。 改写 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.