top of page

A JSON Parsing Class Generator

At Houzz, we’ve developed a solution for JSON parsing in Swift that can save developers a lot of time while supporting all the great characteristics of Swift – namely, type safety, marking model properties as read only, and supporting pure Swift classes (not derived from Objective C) and Swift structs.

The difficulty with parsing JSON in Swift is that it is a strongly typed language, while JSON is loosely typed and JSON tags don’t carry type information and can carry a payload of any type. This requires quite cumbersome code to perform all the needed type checks and to handle all the fallbacks. An added difficulty comes from Swift’s strict adherence to its two-phase initialization, requiring you to initialize all class properties before calling methods on it.

There are several open source libraries already available for parsing JSON in Swift. The first type of libraries simply manage a lot of the complexity in type checking the JSON. These include libraries like Unbox, SwiftyJSON or JSONCodable. While they do reduce the complexity in parsing, they also involve writing a lot of boilerplate code, which we wanted to avoid. In addition, we wanted the solution to also support NSCoding and NSCopying out of the box, as our objective C-based solution does.

In objective C we used a runtime introspection-based solution, similar to JSONModel, except it is a homegrown solution tailored to our needs. The advantage of such a solution is that there is no boilerplate code needed. Just declare the properties you expect to get from the JSON and the runtime introspection will populate the properties based on the data read from JSON. Similar solutions exist in Swift, such as EVReflection. All you need to do is inherit from the EVReflection base object (which was another downside for us, as we wanted to control the class hierarchy) and define the properties you want to extract from the JSON. The downside is that in order for this to work, all the properties need to be defined as read/write, that is as var properties. We wanted to clearly mark in the code which properties came from our server and are part of our data model, and therefore shouldn’t be written to locally. Swift requires these to be var properties due to the init phases in Swift, and the compiler verifying all properties were initialized in the class init. This limitation eliminated any dynamic runtime solution for us.

A third type of solution is graphical tools that can parse the JSON and code generate Swift classes from the JSON. SwiftyJSONAccelerator and JSONExport are examples of such tools. The problem with these solutions is that our JSON API evolves over time, so our objects add properties as the API and data model evolves. We wanted a quick and easy way to update our existing models. Also, being developers, using a text-based solution, preferably similar to source code we know, that is easily source-controlled, is more in our nature.

These led us to the solution we finally chose. We used a code generator to create all the boilerplate code for us, based on a Swift-like class definition, and used Xcode’s built-in ability to define build steps to automatically add the code generation into our build flow. Using this solution, we don’t spend time writing parsers, we just declare our model properties in the most natural syntax for developers, a Swift-like file.

The Cast File We define our classes in a .cast file which has a Swift-like syntax. It will also use the Xcode syntax highlighting, and as we’ve pointed out, properties can be either defined as let or var. So let’s jump into the first, simplest class example:

class Person: NSObject, DictionaryConvertible {
    let firstName: String
    let lastName: String = "x"
    let middleName: String?
    let yearsOld: Int //! "age"
}

This defines a Person class. It derives from NSObject, and by indicating that it conforms to the DictionaryConvertible protocol, the code generator knows this is a class for which an init(dictionary:)? needs to be generated. The DictionaryConvertible protocol defines two methods, the init which gets a dictionary parameter (a dictionary that is the result of NSJSONSerialization), and a DictionaryRepresentation method which does the opposite – given an object, it returns its dictionary representation. It also defines an overloaded init(json:)? Initializer that can get a JSON parameter either as a Data or as a String object.

The Person class defines a required property, firstName. This is non-optional – if the dictionary doesn’t have a “firstName” key, the init will fail and return nil. The Person class also contains a lastName optional property. If the dictionary doesn’t have a “lastName” key, the lastName will get a default value of “x”. The last property, “middleName,” is also optional. If the dictionary is missing this key, middleName will be nil. The last property, “yearsOld,” is an Int but is read from the dictionary key “age”. The //! is the form to add special directives to the code generator. So //! “Key” is how you override the default property name to key name mapping. You can also use this to pick up nested keys. //! “Outer/inner” will look in the dictionary for the key “Outer” which should have a dictionary value, this dictionary should have the key, “inner.” The reason we chose a failable initializer rather than a throwing one is since we do still have objective C in our code, and our models sometimes need to be called from objective C as well, a fail-able initializer is compatible while a throwing one isn’t.

We also wanted our model classes to automatically support NSCoding and NSCopying. Our objective C JSON serialization solution did that, and we find this useful, particularly in save state when you have to serialize the app’s data. To support NSCoding and NSCopying, simply declare your class as conforming to these protocols. The code generator will detect this and generate the required methods to conform to these protocols. In order to do this, your class must derive from NSObject, which raises another feature of the code generator – there is no need to derive from NSObject just for conformance to the DictionaryConvertible protocol. A pure Swift class will work as well. In fact, you can also declare your object as a struct.

Integrating into Xcode To build the cast file, one needs to add a build rule to Xcode. This is done from the build rules tab in the project settings as follows:

image