The iPhreaks podcast episode with Josh Brown was a good insight into JSON parsing in Swift. I didn't think it could be possible to have enough material for an entire book, but it turns out there is plenty.
There were plenty of good tips covered in the podcast, but I'd like to share one way of parsing JSON that wasn't mentioned, by leveraging failable initialisers in Swift. Let's start with an example JSON, for a golf course.
This could be modelled in the following way
It's a simple model, a golf hole has three numerical values for hole number, length and par value, the golf course itself has a name and an array of holes.
At this point we are going to look at how to parse the data in the JSON file into the model. The iPhreaks podcast discussed using parsers to get this done, but I prefer to have the parsing of the data into the model done by the model itself. This is where the failable initialiser comes into play. We can put the parsing into an extension of the model like this
We're also using one of the really nice tricks Josh Brown discussed, typealias is used to define the format for the JSON structures. Using initialisers like this allows me to create the model directly
We can't create a GolfCourse object unless the JSON conforms to the formats required by both GolfCourse and Hole.
But we can now take this further. Above we have each struct defining their own typealias for the input format for the initialiser. We can modify the definition of the format without any other code having to be changed to support this change. In this example we know that Hole only ever accepts integer values in the JSON, so we can make this more concrete by modifying it's format typealias to only allow [String: Int] types.
The GolfCourse logic hasn't changed, it doesn't (and shouldn't) need to. But while this might look like a minor change it has a number of effects. For one, the code is less cluttered, the guard statement isn't forced to cast each time to Int, because the initialiser won't be called if the inputs aren't the correct format. This means that the code fails earlier, as we can see if the input JSON is changed (here I've made the first hole number a string)
This shows that my code employs a particularly unforgiving way of parsing. If the Hole JSON changes to include a string field everything will fail, but that can be changed to revert the Format back to a more liberal [String: AnyObject].
But that [String: AnyObject] is the other extreme. It's too forgiving. I could have a UIViewController in my dictionary and it would be accepted as an input to my parser for the GolfCourse. Ok, it would still result in a nil object, but I'm not happy with it even trying to create an object in the first place.
Ideally I'd like to restrict the input to be something like this
unfortunately, this doesn't work. I'm still working on why. Oh well, onwards and upwards.