iOS开发——网络编程Swift篇&(八)SwiftyJSON详解

SwiftyJSON详解

 

最近看了一些网络请求的例子,发现Swift在解析JSON数据时特别别扭,总是要写一大堆的downcast(as?)和可选(Optional),看?号都看花了。随后发现了这个库SwiftyJSON,问题迎刃而解,灰常优雅和Swifty!

简单介绍下这个库(内容译自SwiftyJSONREADME):

为什么典型的在Swift中处理JSON的方法不好?

Swift语言是一种严格的类型安全语言,它要求我们显示的设置类型,并帮助我们写出更少bug的代码。但是当处理JSON这种天生就是隐式类型的数据结构,就非常麻烦了。

拿Twitter中timeline API返回的数据为例:

 1 [
 2   {
 3     ......
 4     "text": "just another test",
 5     ......
 6     "user": {
 7       "name": "OAuth Dancer",
 8       "favourites_count": 7,
 9       "entities": {
10         "url": {
11           "urls": [
12             {
13               "expanded_url": null,
14               "url": "http://bit.ly/oauth-dancer",
15               "indices": [
16                 0,
17                 26
18               ],
19               "display_url": null
20             }
21           ]
22         }
23       ......
24     },
25     "in_reply_to_screen_name": null,
26   },
27   ......]

 

Swift中的解析代码会是这样:

 1 let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(dataFromTwitter, options: NSJSONReadingOptions.MutableContainers, error: nil)
 2 if let statusesArray = jsonObject as? NSArray{
 3     if let aStatus = statusesArray[0] as? NSDictionary{
 4         if let user = aStatus["user"] as? NSDictionary{
 5             if let userName = user["name"] as? NSDictionary{
 6                 //终于我们得到了`name`
 7 
 8             }
 9         }
10     }
11 }

 

不好吧。就算是换成可选链式调用,也还是一团糟:

1 let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(dataFromTwitter, options: NSJSONReadingOptions.MutableContainers, error: nil)
2 if let userName = (((jsonObject as? NSArray)?[0] as? NSDictionary)?["user"] as? NSDictionary)?["name"]{
3   //上面这一堆是个啥??
4 }

 

使用SwiftyJSON

你只要这样做就行了:

let json = JSONValue(dataFromNetworking)
if let userName = json[0]["user"]["name"].string{
  //恩~ `name`到手,就这么简单
}

你不需要考虑可选类型的拆包和是否能拆包的判断,这些都自动完成了:

 1 let json = JSONValue(dataFromNetworking)
 2 if let userName = json[999999]["wrong_key"]["wrong_name"].string{
 3   //冷静,嘿嘿~ 调用不存在的["wrong_key]也不会crash滴, .string最终能安全的返回一个字符串或`nil`
 4 }
 5 
 6 let json = JSONValue(jsonObject)
 7 switch json["user_id"]{
 8 case .JString(let stringValue):
 9     let id = stringValue.toInt()
10 case .JNumber(let doubleValue):
11     let id = Int(doubleValue)
12 default:
13     println("ooops!!! JSON Data is Unexpected or Broken")

 

后记:SwiftyJSON是怎么做到的?

看到这个库之后,一方面很爽终于有合适的处理JSON的方法了;另一方面心里其实很好奇它是怎么做到的?

通过看源代码,才了解到它是创建了一个JSONValue枚举,这个枚举中有一个JInvalid类型。当使用json字符串来构造JSONValue对象时,如果无法构建成功,就会返回这个JInvalid枚举对象,然后对这个JInvalid枚举对象继续处理,会继续返回JInvalid。直到对其调用string, number, bool之类来获取Swift中的数据类型值时,才会返回nil

这套机制是类似于Optional<T>可选类型的,但是不同的是,Optional中对nil调用方法会crash,但JSONValue中对JInvalid调用方法不会crash,而是继续返回JInvalid。这样使用时就不用写一堆?号啦,反正不会出错滴。

同时,它给JSONValue枚举还创建了其它json中使用到的各种类型JNumber, JString, JBool,它们能通过构造器将原始值包装起来,然后最后通过对应的numberstringbool等属性方法来拆包,得到原始值。

推荐大家也读读这个库的源代码,其对enum的使用灰常巧妙!

json-swift 和 SwiftyJSON 的比较

最近微博上 @SwiftLanguage 让我对这两个库做个简单比较,所以就有了下文:

json-swiftSwiftyJSON都使用了一个自定义的枚举类型来描述JSON数据;通过重载实现了类似Array和Dictionary的下标操作;并可以将NSData类型的json实例转换成其对应的枚举类型的实例。

它们都解决了原来访问JSON类型数据时,必须手动downcast的繁琐操作,如原来要json[“blogs”]? as? Array,现在json[“blogs”]?即可。SwiftyJSON更进一步,连?也可以省掉,使用上更接近Objective-C风格;json-swift保留了?,跟Swift整体风格一致,此外还提供了直接从字面值实例化的便捷操作。

关于用不用?,我个人倾向于SwiftyJSON的做法。其最大优势是可以省写很多?号,无论写程序还是看程序都变得更简单直观;同时由于json结构中数据类型本身就是动态的,如果把每次取值当做一次操作,那么取值的过程不那么type-safe我认为可以接受,只要最终能保证取值结果跟Swift兼容(可选类型)。

 

if you love it, please page to :https://github.com/SwiftyJSON/SwiftyJSON

 

  1 Why is the typical JSON handling in Swift NOT good?
  2 
  3 Swift is very strict about types. But although explicit typing is good for saving us from mistakes, it becomes painful when dealing with JSON and other areas that are, by nature, implicit about types.
  4 
  5 Take the Twitter API for example. Say we want to retrieve a users "name" value of some tweet in Swift (according to Twitters API https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline).
  6 
  7 The code would look like this:
  8 
  9 let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
 10 
 11 if let statusesArray = JSONObject as? [AnyObject],
 12    let status = statusesArray[0] as? [String: AnyObject],
 13    let user = status["user"] as? [String: AnyObject],
 14    let username = user["name"] as? String {
 15     // Finally we got the username
 16 }
 17 
 18 Its not good.
 19 
 20 Even if we use optional chaining, it would be messy:
 21 
 22 let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
 23 
 24 if let username = (((JSONObject as? [AnyObject])?[0] as? [String: AnyObject])?["user"] as? [String: AnyObject])?["name"] as? String {
 25     // What a disaster
 26 }
 27 
 28 An unreadable mess--for something that should really be simple!
 29 
 30 With SwiftyJSON all you have to do is:
 31 
 32 let json = JSON(data: dataFromNetworking)
 33 if let userName = json[0]["user"]["name"].string{
 34   //Now you got your value
 35 }
 36 
 37 And dont worry about the Optional Wrapping thing. Its done for you automatically.
 38 
 39 let json = JSON(data: dataFromNetworking)
 40 if let userName = json[999999]["wrong_key"]["wrong_name"].string{
 41     //Calm down, take it easy, the ".string" property still produces the correct Optional String type with safety
 42 } else {
 43     //Print the error
 44     println(json[999999]["wrong_key"]["wrong_name"])
 45 }
 46 
 47 Requirements
 48 
 49     iOS 7.0+ / Mac OS X 10.9+
 50     Xcode 6.1
 51 
 52 Integration
 53 CocoaPods (iOS 8+, OS X 10.9+)
 54 
 55 You can use Cocoapods to install SwiftyJSONby adding it to your Podfile:
 56 
 57 platform :ios, 8.0
 58 use_frameworks!
 59 
 60 target MyApp do
 61     pod SwiftyJSON, ~> 2.2.0
 62 end
 63 
 64 Note that it needs you to install CocoaPods 36 version, and requires your iOS deploy target >= 8.0:
 65 Carthage (iOS 8+, OS X 10.9+)
 66 
 67 You can use Carthage to install SwiftyJSON by adding it to your Cartfile:
 68 
 69 github "SwiftyJSON/SwiftyJSON" >= 2.2.0
 70 
 71 Manually (iOS 7+, OS X 10.9+)
 72 
 73 To use this library in your project manually you may:
 74 
 75     for Projects, just drag SwiftyJSON.swift to the project tree
 76     for Workspaces, include the whole SwiftyJSON.xcodeproj
 77 
 78 Usage
 79 Initialization
 80 
 81 import SwiftyJSON
 82 
 83 let json = JSON(data: dataFromNetworking)
 84 
 85 let json = JSON(jsonObject)
 86 
 87 if let dataFromString = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
 88     let json = JSON(data: dataFromString)
 89 }
 90 
 91 Subscript
 92 
 93 //Getting a double from a JSON Array
 94 let name = json[0].double
 95 
 96 //Getting a string from a JSON Dictionary
 97 let name = json["name"].stringValue
 98 
 99 //Getting a string using a path to the element
100 let path = [1,"list",2,"name"]
101 let name = json[path].string
102 //Just the same
103 let name = json[1]["list"][2]["name"].string
104 //Alternatively
105 let name = json[1,"list",2,"name"].string
106 
107 //With a hard way
108 let name = json[].string
109 
110 //With a custom way
111 let keys:[SubscriptType] = [1,"list",2,"name"]
112 let name = json[keys].string
113 
114 Loop
115 
116 //If json is .Dictionary
117 for (key: String, subJson: JSON) in json {
118    //Do something you want
119 }
120 
121 The first element is always a String, even if the JSON is an Array
122 
123 //If json is .Array
124 //The `index` is 0..<json.count‘s string value
125 for (index: String, subJson: JSON) in json {
126     //Do something you want
127 }
128 
129 Error
130 
131 Use a subscript to get/set a value in an Array or Dictionary
132 
133 If the json is:
134 
135     an array, the app may crash with "index out-of-bounds."
136     a dictionary, it will get nil without a reason.
137     not an array or a dictionary, the app may crash with an "unrecognised selector" exception.
138 
139 It will never happen in SwiftyJSON.
140 
141 let json = JSON(["name", "age"])
142 if let name = json[999].string {
143     //Do something you want
144 } else {
145     println(json[999].error) // "Array[999] is out of bounds"
146 }
147 
148 let json = JSON(["name":"Jack", "age": 25])
149 if let name = json["address"].string {
150     //Do something you want
151 } else {
152     println(json["address"].error) // "Dictionary["address"] does not exist"
153 }
154 
155 let json = JSON(12345)
156 if let age = json[0].string {
157     //Do something you want
158 } else {
159     println(json[0])       // "Array[0] failure, It is not an array"
160     println(json[0].error) // "Array[0] failure, It is not an array"
161 }
162 
163 if let name = json["name"].string {
164     //Do something you want
165 } else {
166     println(json["name"])       // "Dictionary[\"name"] failure, It is not an dictionary"
167     println(json["name"].error) // "Dictionary[\"name"] failure, It is not an dictionary"
168 }
169 
170 Optional getter
171 
172 //NSNumber
173 if let id = json["user"]["favourites_count"].number {
174    //Do something you want
175 } else {
176    //Print the error
177    println(json["user"]["favourites_count"].error)
178 }
179 
180 //String
181 if let id = json["user"]["name"].string {
182    //Do something you want
183 } else {
184    //Print the error
185    println(json["user"]["name"])
186 }
187 
188 //Bool
189 if let id = json["user"]["is_translator"].bool {
190    //Do something you want
191 } else {
192    //Print the error
193    println(json["user"]["is_translator"])
194 }
195 
196 //Int
197 if let id = json["user"]["id"].int {
198    //Do something you want
199 } else {
200    //Print the error
201    println(json["user"]["id"])
202 }
203 ...
204 
205 Non-optional getter
206 
207 Non-optional getter is named xxxValue
208 
209 //If not a Number or nil, return 0
210 let id: Int = json["id"].intValue
211 
212 //If not a String or nil, return ""
213 let name: String = json["name"].stringValue
214 
215 //If not a Array or nil, return []
216 let list: Array<JSON> = json["list"].arrayValue
217 
218 //If not a Dictionary or nil, return [:]
219 let user: Dictionary<String, JSON> = json["user"].dictionaryValue
220 
221 Setter
222 
223 json["name"] = JSON("new-name")
224 json[0] = JSON(1)
225 
226 json["id"].int =  1234567890
227 json["coordinate"].double =  8766.766
228 json["name"].string =  "Jack"
229 json.arrayObject = [1,2,3,4]
230 json.dictionary = ["name":"Jack", "age":25]
231 
232 Raw object
233 
234 let jsonObject: AnyObject = json.object
235 
236 if let jsonObject: AnyObject = json.rawValue
237 
238 //convert the JSON to raw NSData
239 if let data = json.rawData() {
240     //Do something you want
241 }
242 
243 //convert the JSON to a raw String
244 if let string = json.rawString() {
245     //Do something you want
246 }
247 
248 Literal convertibles
249 
250 For more info about literal convertibles: Swift Literal Convertibles
251 
252 //StringLiteralConvertible
253 let json: JSON = "I‘m a json"
254 
255 //IntegerLiteralConvertible
256 let json: JSON =  12345
257 
258 //BooleanLiteralConvertible
259 let json: JSON =  true
260 
261 //FloatLiteralConvertible
262 let json: JSON =  2.8765
263 
264 //DictionaryLiteralConvertible
265 let json: JSON =  ["I":"am", "a":"json"]
266 
267 //ArrayLiteralConvertible
268 let json: JSON =  ["I", "am", "a", "json"]
269 
270 //NilLiteralConvertible
271 let json: JSON =  nil
272 
273 //With subscript in array
274 var json: JSON =  [1,2,3]
275 json[0] = 100
276 json[1] = 200
277 json[2] = 300
278 json[999] = 300 //Don‘t worry, nothing will happen
279 
280 //With subscript in dictionary
281 var json: JSON =  ["name": "Jack", "age": 25]
282 json["name"] = "Mike"
283 json["age"] = "25" //It‘s OK to set String
284 json["address"] = "L.A." // Add the "address": "L.A." in json
285 
286 //Array & Dictionary
287 var json: JSON =  ["name": "Jack", "age": 25, "list": ["a", "b", "c", ["what": "this"]]]
288 json["list"][3]["what"] = "that"
289 json["list",3,"what"] = "that"
290 let path = ["list",3,"what"]
291 json[path] = "that"
292 
293 Work with Alamofire
294 
295 SwiftyJSON nicely wraps the result of the Alamofire JSON response handler:
296 
297 Alamofire.request(.GET, url, parameters: parameters)
298   .responseJSON { (req, res, json, error) in
299     if(error != nil) {
300       NSLog("Error: \(error)")
301       println(req)
302       println(res)
303     }
304     else {
305       NSLog("Success: \(url)")
306       var json = JSON(json!)
307     }
308   }

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。