Building a JSON parser with TDD (Part 2) — String Theory

We finished the last post finishing parsing Ints. But JSON numbers can also be floating points. So let’s resume where we left and move to green phase.

Let’s add the test for floating points, or Doubles in Swift.

Moving straight to red.

It should be easy to handle it with the knowledge we have acquired so far. We are smart now! Let’s add the Double initializer to the optional chain.

It worked! All tests pass. We’re smart!

Time to do some refactoring in our tests. First, force casting everywhere is bothering me. Let’s create some helpers.

This assert helper is pretty straightforward. We try to cast the parsed Any? to the generic type T. If it fails, the test fails. We can now add some more info about why it failed in the XCTFail description. This helper doesn’t work for our validations for nil, so we create another one: assertNil.

Refactoring done. Let’s move on

Next parsed type we’re handling is String. Strings in JSON are just characters surrounded by double quotes. Most degenerate String is an empty string, which of course should be parsed as an empty string.

Let’s add a test to validate it for us.

Straight to red.

Let’s try to do it the easy way. If it’s not Bool, Int or Double, return empty string! Will it work? Only one way to know. Running tests…

So the nil test fails. See, there is a reason for a test as dumb as the nil test, which was our first test to pass! Now it helped to keep us from breaking things. Most of the time when we work without TDD, we start adding new cases in our code and forget about how it should handle invalid/nil cases.

Since now we have something more complex, let’s try to create a helper private function for string parsing. This function should return the string if it is a string, otherwise, return nil. Let’s do it the easy way.

I love how tuple matching keeps the code simple and short. Note that we had to add ? to the patterns due to text.first and text.last being Optional. We run the tests and… Pass! At least with an empty string, which doesn’t help much. Let’s move on to a string with actual content inside

Wait, before we proceed, something stinks about this text.first and text.last. We are validating that the first and last are a double quote, but what if the string is one character long with a double quote. This makes first and last the same character, and the one that we’re actually expecting. Let’s try it out.

Test fails! Moving to red again.

So a string with one double quote is actually being parsed as an empty string. We do not want that. Let’s try to fix this the easy way. Adding a guard.

A simple guard saved us. Tests pass now!

Let’s resume trying to parse a string with content. First we create our test.

Of course it fails.

So strings are basically a bunch of characters surrounded by double quotes. Pretty easy to parse. Let’s remove the first and last character from the JSON and it should work!

Hm, compiling error. dropFirst and dropLast in fact return a Substring, not a String. This is done to improve performance in string operations. A Substring has a reference to the original string, so it doesn’t need to copy it. For now, let’s make it simple and create a String from the Substring.

Tests pass!

We could now do more tests for strings, but we are in green phase, so our goal is to move to red, and I can’t think of a simple test that would break our code. Time to move forward then.

In the next post, we’ll start handling Array parsing. It’s probably going to be more of the same… Or will it be?

The final code for this post is available on GitHub