JSON parsing in Golang
Parsing JSON is a common task for most developers, currently I'm actually finally getting to write slugofy which is supposed to slugify URLs or URL fragments in Go.
In case you're wondering, slugs are commonly used to generate URLs from the title of a page. Let's say we have a page title like the Swedish national dish: Swedish national dish: köttbullar
, we probably don't want either the special chars like the ö or the : in there. Also we don't want whitespaces to show up as %20
, so let's make them dashes instead: swedish-national-dish-koettbullar
.
For that I needed JSON parsing, because I'm making use of the fantastic rule set from the PHP project slugify.
First I had to figure out a way to get data from a JSON file into some kind of data structure in Go, most examples use a struct
for that purpose, but let's start off with our example JSON file:
[
{
"name": "Nostromo",
"source": "Alien"
},
{
"name": "Serenity",
"source": "Firefly"
},
{
"name": "Canterbury",
"source": "The Expanse"
}
]
Now let's produce an output like the following:
The ship 'Nostromo' first appeared on 'Alien'
The ship 'Serenity' first appeared on 'Firefly'
The ship 'Canterbury' first appeared on 'The Expanse'
To do that, you bascially need to:
- read the JSON file
- parse the JSON
- loop over your data structure
- in each step, output a formatted string
I do that with the following code (feedback welcome):
package main
import (
"fmt"
"os"
"encoding/json"
"io/ioutil"
)
type shipObject struct {
Name string
Source string
}
func main() {
filePath := "./ships.json";
fmt.Printf( "// reading file %s\n", filePath )
file, err1 := ioutil.ReadFile( filePath )
if err1 != nil {
fmt.Printf( "// error while reading file %s\n", filePath )
fmt.Printf("File error: %v\n", err1)
os.Exit(1)
}
fmt.Println( "// defining array of struct shipObject" )
var ships []shipObject
err2 := json.Unmarshal(file, &ships)
if err2 != nil {
fmt.Println("error:", err2)
os.Exit(1)
}
fmt.Println( "// loop over array of structs of shipObject" )
for k := range ships {
fmt.Printf( "The ship '%s' first appeared on '%s'\n", ships[k].Name, ships[k].Source );
}
}
The parsing happens through json.Unmarshal
and it expects the struct to have properties in an uppercase version of the JSON keys of each object, so name
from the JSON file becomes Name
as the struct property.
Now, if we have a look at the resources files from the PHP project slugify, we'll see that we actually have keys that are the character to replace when slugifying and the value, what that character should become in ASCII characters, see the norwegian special chars below:
{
"Æ": "AE",
"Ø": "OE",
"Å": "AA",
"æ": "ae",
"ø": "oe",
"å": "aa"
}
So for this purpose, another data structure in Go is more appropriate, the map
data type, since we don't want to create a key in our struct for every character.
Let's have a look at an output like the following:
'Æ' becomes: 'AE'
'Ø' becomes: 'OE'
'Å' becomes: 'AA'
'æ' becomes: 'ae'
'ø' becomes: 'oe'
'å' becomes: 'aa'
The code below does just that:
package main
import (
"fmt"
"os"
"encoding/json"
"io/ioutil"
)
func main() {
filePath := "./norwegian.json"
fmt.Printf( "// reading file %s\n", filePath )
file, err1 := ioutil.ReadFile( filePath )
if err1 != nil {
fmt.Printf( "// error while reading file %s\n", filePath )
fmt.Printf("File error: %v\n", err1)
os.Exit(1)
}
var replacementPairs map[string] string
err2 := json.Unmarshal(file, &replacementPairs)
if err2 != nil {
fmt.Println("error:", err2)
}
for k := range replacementPairs {
fmt.Printf( "'%s' becomes: '%s' \n", k, replacementPairs[k] )
}
}
As you can see inside the Unmarshal
function we use the map that we defined that has string
as the type for both key and value. The fact that now the data from the JSON document are in a data structure in Go where we can iterate over it, means that we can also loop over a string and check for the occurrence of a key, to then promptly replace it with a value.
Since I haven't really published slugofy yet, because I'm a massive Go noob, I'll surely blog more about Go and next up: replacing stuff in strings and regular expressions (regExp) in Go!