Consuming JSON in Swift (hmm 3) sans Cocoapods Part 2

This article is code-sponsored by Rob Napier.

Rajiv Jhoomuck
4 min readMar 18, 2017

Here is the story

I had sent the first part of this series to Rob Napier (and other developers) for review. In his reply Rob told me that I should not postpone the error handling part to a later post.

I see you’re pushing error handling off till a later post, but using throws makes usage simpler rather than more complicated IMO, while adding only a small bit of complexity to the value function. — Rob Napier

In his message he linked to his solution that augments this technique of JSON decoding:

Rob’s solution turns out to be better than mine where he separates the retrieval of values from JSON into two methods with very clear names. So let’s go through the code.

Since we have decided to reduce our leniency towards our collegues on the web team and provide better feedback to clients of our API, in this article, we need to define an error that we will pass to clients of our API. We want to provide as much feedback as we can to help our clients diagnose issues. Therefore we will have an associated value to the error that we will throw. That associated value will be the key that does not have its matching pair inside the JSON being parsed.

public enum JSONInitializableError: Error {
case initializationError(key: String)
}

The only modification that we need to bring in the definition of the JSONInitializable protocol now is change the initializer from being a failable to one that will throw our previously defined error:

init(with json: JSON) throws.

Moving forward, we need to make a change on the extension of JSONInitializable protocol. In the last post, value(for jsonKey: Key in jsonObject: JSON) would return a value if it exists else just nil. This is definitely is a behavior that we will keep in our protocol to cater for properties that are optional, (Remember address which is marked as optional on Person). But we will explicitly shift this responsibility of optionality to another function with an appropriate name. [We are aiming for clarity here]. value(for jsonKey: Key in jsonObject: JSON) will still be used to extract values from JSON objects but now it will throw a JSONInitializableError should it fail to retrieve a valid value.

Value retriever function that throws

Next we define the appropriately named optionalValue(for jsonKey: Key in jsonObject: JSON) which will provide the responsibility that value(for… has been providing so far but with a slight twist.

Optional value retrieving function

This function will slyly just call value(for jsonKey: Key in jsonObject: JSON) with a try? that will hush up any potential error and instead return nil. Pretty straight forward.

Note: We could have used a try? at call site, in the initializers of our models, but putting that in a function that has an explicit name is documentation for clients using this protocol.

Done!

With all this in place, here is how our models will look like:

Models (revamped)

In fact, as Rob said, this is even better as we do not have temporary variables that will store required properties’s values that will be re-assigned to the properties later.

Using this revamped protocol in our code

  1. Being Lenient with received JSON stream

A first attempt to decode persons might result in the following code:

First attempt at parsing JSON

But the above snippet has one flaw. It fails completely and returns nil if any one of the JSON object fails. Go ahead and try it!. A better way to do that is like this:

A better way to generate our model objects

In this way we satisfy the rules that we had set in the last article. But in this article, we have ambition. We want to give a client the chance to handle the issues contained in our JSON.

2. Strict but lenient at the same time

Say we want to continue working with persons that we have been able to extract but (for simplicity sake) log JSON objects that do not have correct keys and that fail initialization.

Strict but lenient 🤷‍♂️

3. Strict

Last case say we discard the entire list for any discrepancy in the JSON objects.

Strict

Conclusion

The goal of this series is two-fold:

  1. Prove that JSON parsing is not scary. With some simple constructs we can reliably decode that into our models
  2. Before resorting to third-party libraries, always take some time to evaluate the real problem. (In the case of JSON decoding read this series and share on your favorite social metwork 👹).

Someone asked me about testing this functionality. Turns out the quick way to do that is to make your XCTestCase subclass conform to that protocol (instead of adding a property that conforms to the protocol and complicate your life). Then write some tests to see that the functions in the extension correctly grabs the intended values.

Hope this article was helpful. Again thanks to Rob Napier for his amazing feedback and code-sponsorship for this part of the series.

--

--