Simple Library Free JSON Parsing in Swift

Parsing JSON in Swift without adding libraries could be tricky and annoying at least until nil coalescing was added and optional chaining was upgraded late in the original betas but that really isn't the case at least since Swift 1.0 was final. There are clearly a wide range of JSON libraries for Swift ranging from SwiftyJSON which just tidies up extractions from deep in nested structures to Argo which will feel comfortable for those coming from Haskell and look clever but be hard to read for those not familiar with the syntax.

I want to show people that there is really nothing very tricky or ugly about parsing JSON data in Swift even without libraries to help.

This post was trigger by seeing Jameson Quave's new lightweight Luma library and the syntax in the project readme (at the time of writing) would work without a added library on the result of parsing with Cocoa's included NSJSONSerialization class. Please note that there is nothing wrong with the Luma library or anything I have seen written about it but I wanted to make clear how lightweight it is and how easy these things are to do without using a library at all.

I confirmed that the syntax was available in the Playground without using the library and there is no problem just using exactly the same syntax on the dictionary you get back from NSJSONSerialization as in the Luma example. The only difference in the presentation of printing the parsed structures.

The only particular trick that I would recommend particularly if you are parsing more complicated JSON with nested dictionaries (objects in JSON terminology) would be to define a typealias for [String:AnyObject] and you may want to cast to it or return it in places and it gets a little cumbersome to type.

The key bit of Swift syntax is the fact that you can use optional chaining on dictionary subscripts so that you can even extract data from deeply nested objects with a single if let condition.

The advantages of using this approach is that you can get more information on errors, it works directly on NSData (which is the common case when the JSON is coming from a file or from the network). The disadvantage is that you must manually deal with the Objective-C pattern of passing an NSError variable by reference but that isn't too difficult and you can easily wrap that in a helper function such as this:

Whether you use a result type or just return optionals is up to you (and you may already have a generic result or either type that will do the job for you). Using a Result type means that illegal states (nil error and nil result or both an error and a result) can't be represented and don't need to be handled.

This is a more complicated example that I am using to parse the results of the Star Wars API
and in particular the Starships query which returns an object with a "results" array containing the starships. There is also the issue that the length values are returned as strings so need separate parsing to be used as number values (in this case as NSDecimalNumbers). Again only Foundation and the Swift standard library are used. Fetching the data is not included (that is another post to be written). Note that this example uses the new Swift 1.2 if let syntax that is available in Xcode 6.3 Betas.

The nesting does get a little two deep in this case. In actual production code I tend to extract the creation of each object or struct from the JSON to an additional method (often a failable initializer) so leave the code like this (which should work in Swift 1.1 in addition to Swift 1.2.

With this approach you could also have some optional properties of your struct and decide on a per-property basis whether the init should fail or continue if there isn't a correctly typed item in the dictionary for it. It is up to you to decide what is optional in your data model. In the example I am strict and all properties are required or the init fails.

Again as discussed earlier more advanced error types could be used rather than just nils but this code is robust and won't crash although if there are parse errors it won't do much or will be missing some data. Whether these are the appropriate choices will depend on your scenario, sometimes you will want to fallback in other ways or report errors visibly.

The other thing to note is that this code is written for the Playground and to print results. In production the parsing process would be wrapped in a function returning the array of the objects that I want as the result. In the event of errors this array could be empty so that you can always return a correctly typed result or you an make it an optional to distinguish between errors and other empty results or develop a richer return type giving details on errors depending on your requirements.