Since Swift is very strict about types, it can be a hassle working with JSON because it’s naturally implicit about types. SwiftyJSON is an open source library that helps you use JSON in Swift without tearing your hair out. Before getting started, let’s take a closer look at just how painful handling JSON in Swift can be.
The Problem with JSON in Swift
Take the Twitter API for example. Retrieving a user’s “name” value from a tweet in Swift should be pretty simple. Here’s what we’re dealing with in JSON:
[
{
......
"text": "just another test",
......
"user": {
"name": "OAuth Dancer",
"favourites_count": 7,
"entities": {
"url": {
"urls": [
{
"expanded_url": null,
"url": "http://bit.ly/oauth-dancer",
"indices": [
0,
26
],
"display_url": null
}
]
}
......
},
"in_reply_to_screen_name": null,
},
......]
In Swift, you’d have to use this mess:
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(dataFromTwitter, options: NSJSONReadingOptions.MutableContainers, error: nil)
if let statusesArray = jsonObject as? NSArray{
if let aStatus = statusesArray[0] as? NSDictionary{
if let user = aStatus["user"] as? NSDictionary{
if let userName = user["name"] as? NSDictionary{
//Finally We Got The Name
}
}
}
}
Or, you could take another approach, but it’s hardly easy to read:
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(dataFromTwitter, options: NSJSONReadingOptions.MutableContainers, error: nil)
if let userName = (((jsonObject as? NSArray)?[0] as? NSDictionary)?["user"] as? NSDictionary)?["name"]{
//What A disaster above
}
Getting Started
You can download SwiftyJSON here, or clone it directly from GitHub:
git clone https://github.com/lingoer/SwiftyJSON.git
Basic Usage
Using SwiftyJSON is quite simple:
Say we have some dataFromNetwork: NSData!
produced by a typical NSURLSessionTask fetching the Twitter’s API:
The first thing you should do is to init a JSONValue
:
let json = JSONValue(dataFromNetwork)
A JSONValue
is an enum that represents a typical JSON data structure.
You can retrieve different values from the original JSONValue
just by using subscripts
like this:
let userName:JSONValue = json[0]["user"]["name"]
Note the userName is still a JSONValue
. So, what if we want a String
?
You can use the .string
property to get the actual value of the JSON data it represents.
let userNameString = userName.string!
For each kind of JSON Type, JSONValue
provides a property to retrieve it:
var string: String?
var number: NSNumber?
var bool: Bool?
var array: Array<JSONValue>?
var object: Dictionary<String, JSONValue>?
Note that every property above is an Optional
value. This is because the JSON data can contain any type that’s valid in its definition.
So, the suggested way to practically retrieve value is by using Optional Bindings:
if let name = userName.string{
//This could avoid lots of crashes caused by the unexpected data types
}
if let name = userName.number{
//As the value of the userName is Not a number. It won't execute.
}
The .number
property produces an NSNumber
value, which is usually not very useful in Swift. You can use the .double
and the .integer
to get a Double
value or an Int
value.
if let intValue = numberValue.integer{
count += intValue
}
Enumeration
The JSONValue
is in fact an enum
in Swift:
enum JSONValue {
case JNumber(NSNumber)
case JString(String)
case JBool(Bool)
case JNull
case JArray(Array<JSONValue>)
case JObject(Dictionary<String,JSONValue>)
case JInvalid(NSError)
}
You can use a switch
statement to get values more efficiently:
let json = JSONValue(jsonObject)
switch json["user_id"]{
case .JString(let stringValue):
let id = stringValue.toInt()
case .JNumber(let numberValue):
let id = numberValue.integerValue
default:
println("ooops!!! JSON Data is Unexpected or Broken")
Subscripts
Note that an Array structure in JSON is wrapped into Array<JSONValue>
, which means that everything in the Array is still a JSONValue
. Even if you retrieve an array from JSONValue, you still have to use the basic properties to get the element’s value:
if let array = json["key_of_array"].array{
if let string = array[0].string{
//The array[0] is still a JSONValue!
}
}
The same with the Object. So, the suggested way to access such Arrays and Objects is to always use JSONValue
‘s subscripts.
if let string = json["key_of_array"][0].string{
}
In fact you can always use subscripts to access a JSONValue
without worrying about runtime errors leading to a crash:
let userName = json[99999]["wrong_key"]
And if you use the suggested way to retrieve values, it always safe:
if let userName = json[99999]["wrong_key"]["name"].string{
//It's always safe
}
Printing
The JSONValue
conforms to the protocol Printable
. So it’s easy to get all the JSON data in raw string representation:
let json = JSONValue(dataFromNetwork)
println(json)
/*You can get a well printed human readable raw JSON string:
{
"url": {
"urls": [
{
"expanded_url": null,
"url": "http://bit.ly/oauth-dancer",
"indices": [
0,
26
],
"display_url": null
}
]
}
*/
If you don’t want to just print it out, you can use the .description
property to get the string above.
let printableString = json.description
Debuging And Error Handling
What if JSON data is corrupted or if we retrieve data the wrong way? You can simply use an if
statement to test it:
let json = JSONValue(dataFromNetworking)["some_key"]["some_wrong_key"]["wrong_name"]
if json{
//JSONValue it self conforms to Protocol "LogicValue", with JSONValue.JInvalid stands for false and others stands true
}
If we try to access data through an incorrect key or index, the description can tell you where the KeyPath went wrong.
let json = JSONValue(dataFromNetworking)["some_key"]["some_wrong_key"]["wrong_name"]
if json{
} else {
println(json)
//> JSON Keypath Error: Incorrect Keypath "some_wrong_key/wrong_name"
//It always tells you where your key went wrong
switch json{
case .JInvalid(let error):
//An NSError containing detailed error information
}
}
Final Note
Development on SwiftyJSON will continue here on Github, so keep an eye on it for the latest version.
Author: Roy Fu