Some time ago when Swift was announced there was a big discussion in the iOS development community about JSON deserialisation and the right way to implement it. At the time, I didn’t give it much attention because I was still committed to Objective-C and didn’t have any immediate requirements to use Swift.
Fast forward two years and I’m now mostly programming in Swift and quickly found out the issues around JSON that everyone was talking about two years ago.
The main problems result from the way JSON parsing is done on iOS, the NSJSONSerialization object simply returns an AnyObject for you to handle, and because of Swift is very strictly typed you soon get code that is very verbose and hard to follow.
For example to read the photo URL from a JSON file like this:
[ { "id": 1, "name": "Sergio", "weight": 70.5, "photos": [{ "url": "http://fake.com/sergio.jpeg", "width": 100, "height": 200 }] } ]
You will need to write something like this:
do { guard let url = NSBundle.mainBundle().URLForResource("sample", withExtension: "json"), let JSONData = NSData(contentsOfURL: url), let users = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions()) as? Array<AnyObject> let user = users[0] as? [String: AnyObject], let photos = user["photos"] as? Array<AnyObject>, let photo = photos[0] as? [String: AnyObject], let photoURL = photo["url"] else { print("Ops... some error") } print("\(photoURL)") } catch { print(error) return }
While this is improved from early versions of Swift because of the addition of the guard statement and the new error handling, it’s still very verbose and if you need to track errors on the JSON file you need to split all the guard conditions in order to be able to find where things failed.
So I decided to implement my own library to allow less verbose code while reading values from JSON, but without using any custom operators or protocols like other libraries. Another goal was also to make it easy to find any mistakes made on the JSON files.
The end result was a single class called SVEJSONObject were you can write code like this:
let url = NSBundle.mainBundle().URLForResource("sample", withExtension: "json")! do { let users = try JSONObject(url: url) let photoURL = try users[0]["photos"][0]["url"].string() } catch { print(error) return }
SVEJSONObject allows you to have simpler deserialization code by keeping types restricted to what is available in the JSON specification.
It also allows you to chain subscript calls like this:
users[0]["photos"][0]["url"].string()
And it makes error tracking easy by using Swift exception mechanism with a custom ErrorType that shows what went wrong. For example, if for some reason you tried to do this
users[0]["photos"][0]["link"].string()
you will get an JSONValidationError with this message:
Root[0]->photos->[0]: Missing "link" element in dictionary.
You can check the full code and documentation in the project repo in GitHub.
Leave a Reply