How I wrote a JSON parser in Go - Part 3 - More features

Opemipo · December 20, 2019

In the previous post, I wrote about how I added the iterator abstraction. In this one, I will be discussing how I added new features.

Parsing null, true and false

The first thing I noticed was that to parse null, true and false, I could use the same function which I called loadKeyword. loadKeyword accepts an iterator, the value to parse and the object that should be returned. The function looks like this

func loadKeyword(iter *iterator, keyword string, value interface{}) interface{} {
	for _, val := range keyword {
		if rune(iter.getCurrent()) != val {
			panic(fmt.Sprintf("There was an error while reading in %s", keyword))
		}
		iter.advance()
	}
	return value
}

and these lines were added to the load function.

case iter.getCurrent() == 'n':
    return loadKeyword(iter, "null", nil)
case iter.getCurrent() == 't':
    return loadKeyword(iter, "true", true)
case iter.getCurrent() == 'f':
    return loadKeyword(iter, "false", false)

This means we can now parse these strings correctly

obj := Load("{"k1": true,"k2": false,"k3": null}")
obj := Load("true")
obj := Load("false")
obj := Load("null")

Parsing numbers

I decided to support parsing integers first. This just required writing an isNumber function and a loadNumber function. The code looks like this

func isNumber(iter *iterator) bool {
	switch iter.getCurrent() {
	case '1', '2', '3', '4', '5', '6', '7', '8', '9':
		return true
	}
	return false
}

func loadNumber(iter *iterator) interface{} {
	num := 0
	for !iter.isEnd() && unicode.IsDigit(rune(iter.getCurrent())) {
		num *= 10
		val, _ := strconv.ParseInt(string(iter.getCurrent()), 10, 64)
		num += int(val)
		iter.advance()
	}
	return num
}

For negative numbers, I just checked for the sign at the beginning and then proceeded with parsing the rest of the number and then multiplying by the sign at the end. For decimals, I added logic for checking for ‘.’ and parsing the fractional part. For exponents, I added logic for checking of ‘e’ or ‘E’ and parsing the exponent. The code for this can be found here.

That’s all of the features in the JSON specification. 😄.

Notice that to add these new features I didn’t have to change already working code and I had tests to prove that it all works like expected.

That’s the all, right?

Wrong.

The next step when writing a library is to benchmark it and then optimise. I was curious how my code compared with the standard libraries implementation. This step is the subject of the next blog post

Stay tuned!

Twitter, Facebook