The state of JSON parsing in Swift - Part 1

May 23, 2016

One of the things that becomes apparent, if you poke around Swift Modules looking at packages, is that there are a lot of JSON libraries. Not surprising, because dealing with JSON is a very common task. Still, I read that as a signal of the langauge's youth. With a more mature language and package ecosystem, these type of low level problems are seen as "solved", and developers look for other problems. I thought it would be interesting to explore the libraries that are out there, and summarize and review them for interested readers. Since there are so many libraries, and I want to give an example for each, I'm going to break this into parts. Part one today, check back later or subscribe to the mailing list to get notified when part two is up.

Libraries Reviewed


Part 1

SwiftyJSON

SwiftyJSON has quickly risen to the top of the heap. Widely used and many-starred (over 10k at time of writing). The syntax is clear and expressive, and everything else we look at will have to prove itself to be better and more useful than this:

            let json = JSON(data: data)
            if let userName = json[0]["user"]["name"].string {
                //Now you've got your value
            }
            

On the negative side, it doesn't provide tools for mapping to your model objects. You're still left to do the task of unwrapping and validating your JSON manually. It's also dependant on NSJSONSerialization, while some of the other solutions have their own parser.

Freddy

Written published by the Big Nerd Ranch, Freddy (as in vs. Jason) takes a different approach. Instead of returning optionals, to deal with the possability of missing or bad data, Freddy uses Swift's try-catch syntax. This has the nice effect of eliminating the repeated optional unwrapping.

            do {
                let json = try JSON(data: data)
                let userName = try json.string(0,"user","name")
                // now you've got your value
            } catch {
                // handle bad json
            }
            

It's a nice take on JSON handling, and the only downside is the slightly unusual "path" syntax (json.string(0,"user","name")) that may be confusing to developers used to a more traditional (object["key"]["otherKey"]) syntax.

Jay

If SwiftyJSON uses optionals to express errors, and Freddy uses try-catch, Jay uses a bit of both:

            do {
                let json = try Jay().typesafeJsonFromData(data)
                if let tasks = json.array?[0].dictionary?["users"]?.dictionary?["name"] {
                    // now you've got your value
                }
            } catch {
                // handle bad data
            }
            

We'll look at a few libraries that follow suit with this mixture of try-catch and optionals for error handling. To my eye this is predicable behaviour that's easy to write, but it seems to lead to overly convoluted code. That said, it's no small feat to write a parser from scratch, and the library has some very nice tools for formatting/pretty printing JSON.

Gloss

Gloss is not about going from data to dictionary, it's about going from dictionary to model object. In fact, perhaps confusingly, in one of Gloss' source files there is the line public typealias JSON = [String : AnyObject]. You're left to use NSJSONSerialization or another library to do the initial parsing of JSON, and Gloss steps in to help you build your model objects.

            struct User: Decodable {
                let name: String?
                init?(json: JSON) {
                    self.name = "user.name" <~~ json
                }
            }
            

For my taste, Gloss is a bit too much of a DSL. That is, doing simple things is made VERY easy, but it can be difficult to see how to express anything non-standard without being intimately familiar with the library. That said, doing simple things is VERY easy with Gloss.

Bric-à-brac

Written in pure Swift, not dependant on Foundation, with a streaming parser. Bric-à-brac has a great feature checklist, and it doesn't just parse, is also has helpers for creating your model objects after parsing.

            do {
                let bric: Bric = try Bric.parse(data)
                if let name: String = bric[0]?["user"]?["name"] {
                    // now you've got the data
                }
            } catch {
                // handle error
            }
            

As you can see, the error model is similar to Jay's. A mix of do/try for the initial decode, and optionals for the path traversal. Everything I critiqued about Jay applies here. I'd describe Bric-à-brac as Jay plus some nice bells and whistles.

PureJsonSerializer

Main standout features are 1. written in pure Swift, and 2. it serializes and de-serializes.

            do {
                let json = try Json.deserialize(data)
                if let value = json["Foo"]?["bar"]?.stringValue {
                    // value obtained!
                }
            } catch {
                // handle error
            }
            

As you can see, the error model is similar to Jay and Bric-à-brac. A mix of do/try for the initial decode, and optionals for the path traversal. PJS is also a component of Genome, which is a mapping library that's not yet available via SwiftPM (so we'll look at that later).

Himotoki

The name in Japanese means 'decoding', but that's not all it does. It fills roughly the same task as Gloss, but has a couple of wins that might make you choose it over Gloss. For instance it has support for custom transformer objects, which makes it easy to go from String to NSURL, or String to NSDate, as you create your objects.

            struct User: Decodable {
                let name: String
                static func decode(e: Extractor) throws -> User {
                    return try User(name: e <| [ "user", "name" ])
                }
            }
            
            do {
                let user = try User.decodeValue(JSON)
            } catch {
                // handle error
            }
            

Worth considering if you're not turned off by the operator overloading!

Subscribe to the newsletter, or rss feed to get notified when I review the rest of the Swift JSON parsing world. If you have a suggestion for a library not covered above or listed below get in touch.

Still to come:

〜〜〜

Using Swift modules with the Swift Package Manager

May 12, 2016

Get the tools

All the modules listed on this site are designed to be used with Apple's Swift Package Manager (SPM). The package manager is included in the Swift 3 development snapshots available from swift.org. SPM doesn't currently integrate with Xcode. There are ways to build code using SPM and manually add it to Xcode, but since the modules are usually written for Swift 3 which has some langauge level changes, and Xcode uses Swift 2.2 at the moment, this isn't usually going to work. I expect Swift 3 will come out of beta in June at WWDC 2016, so this will change soon.

Set up your project

Make a new directory for your project. Inside this directory you'll put a Package.swift file that lists your dependencies, and a source folder that will contain all your code. The source folder needs to be named "Sources", "source", "src", or "srcs" for SPM to recognize it. I prefer Sources, but they all work. Add a file to your Sources directory called main.swift. This name is important, as main.swift is the only file that can contain code outside of classes, functions or structs. Put something simple in this main file, perhaps print("hello, swift modules!"). Next, in the root of your project directory, add a Package.swift file. In this file put the following, inserting your project name where apropriate:

import PackageDescription
            
            let package = Package(
               name: "HelloSwiftModules"
            )
            

Now open the command line, cd to your root project directory, and run swift build. This will compile everything in your Sources directory into a single module, and place the resulting binary at .build/debug/HelloSwiftModules. To run it, type the path to the binary and hit enter. You should see "hello, swift modules!" on the console.

Add dependencies!

So far we've not done anything that really requires package management. Our app is a single source file and has no external dependencies. Let's change that! Let's use Kyle Fuller's awesome Commander library to make a command line app. First, we need to add it as a dependency in our Package.swift file. It should look like this:

import PackageDescription
            
            let package = Package(
               name: "HelloSwiftModules",
               dependencies: [
                   .Package(url: "https://github.com/kylef/Commander.git", majorVersion: 0)
               ]
            )
            

Pretty simple, we've specified the git repo to find the code for Commander, and Kyle has put a Package.swift file in the root of that repo, so SPM knows exactly what to do. We've also specified a major version number. If we look at the Commander repo's git tags, we see that the latest version is 0.4.1. This means we're going to get the latest version, but if Kyle releases a 1.0.0 or greater, we'll not automatically upgrade. Now let's use Commander in our main.swift file. Open it up, add import Commander to the top of the file and wrap our print statement in a command block like so:

import Commander
            
            command { (name: String) in
                print("Hello, \(name))
            }.run()
            

Now return to the terminal and run swift build again. This time when we go to run the app (.build/debug/HelloSwiftModules) we get "Missing argument" as output. Our command requires one input term. Run .build/debug/HelloSwiftModules world to satisfy Commander and run our command function.

Run with it!

Now that you've seen the basics, you're all set to start using Swift packages. Be forewarned that Swift 3 has source breaking changes in almost every development snapshot, so you're likely to run into packages that don't work with your version of the toolchain. The perks of living on the edge! But don't dispair, if a package you want to use isn't compiling, check back in a week or so. The developer may have ported the code by then. Check back here, or subscribe to our mailing list for upcoming articles on topics like building your own Swift package and adding it to the Swift Modules index. Happy coding! 📦

〜〜〜

Welcome to Swift Modules

May 11, 2016

Today I've started the soft-launch of Swift Modules, which means sending the link to a few colleages and friends (hi friend!). I'm very excited to launch, but deciding when a project is done is a hard problem by any measure. I have about a hundred features I'd like to include, but if I keep adding features it'll never be finished.

I started this project almost a month ago, and it's been a fun ride. I've had to learn about a dozen new technologies to build it. Notably, React and Elasticsearch, but I've also learned a lot about using Swift as a command line and server side tools. I'll be writing here more after launch about how I built it, but for now just know that it's made of many cool components.

I hope this project will serve the Swift community as a whole. Apple's package manager solves an important problem, and Swift Modules aims to solve the related problem of discovery. Consider signing up for the Swift Modules newsletter below. It'll be quite low volume, I promise! 📬

〜〜〜

Enter your project's GitHub URL

It will be indexed and available for search momentarily. Non-github projects are not supported at the moment.

Error indexing this package

If you think it should have worked, contact support with the link in question.

Thanks!

Your project should be indexed momentarily