Recently, I have been spending a lot of time working with Go. While helping other developers I noticed that many struggled with encoding and decoding JSON. In this guide I cover a lot of the common issues developers run into and how to solve them.
Naming keys
In the following example I will define a struct and then marshal it using the encoding/json
package.
package main
import (
"encoding/json"
"fmt"
)
type Item struct {
Foo bool
Bar string
}
func main() {
itemJson, _ := json.Marshal(&Item{Foo: true, Bar: "Test"})
fmt.Println(string(itemJson))
}
The code above will output the following string.
{"Foo":true,"Bar":"Test"}
The keys Foo
and Bar
are using the same format as our struct. You may want these to be lowercase or different from your struct. To do that we can use struct tags like so.
type Item struct {
Foo bool `json:"foo"`
Bar string `json:"bar_key"`
}
By only changing the struct and running the same code again we will get the following output.
{"foo":true,"bar_key":"Test"}
Excluding keys
We can exclude the keys by adding a -
to our json tag in our struct. Any tags which use a -
will be excluded when marshalling a struct. So let’s change one of our tags to -
like this:
type Item struct {
Foo bool `json:"foo"`
Bar string `json:"-"`
}
When we run our code again we will get the following output.
{"foo":true}
As you can see Bar
is now excluded from the marshalled string. This is useful if you have structs that serve multiple purposes such as database queries and api responses.
Optional or empty keys when unmarshalling
In some situations you may send or receive JSON with optional keys which are sometimes present and sometimes not. The issue you may run into is that bools are false
, ints are 0
and strings are ""
when they are not set. I have seen some developers make huge workarounds to deal with this issue but there is a simple solution: pointers. Let’s take a look at an example without pointers.
package main
import (
"encoding/json"
"fmt"
)
type Item struct {
Foo bool `json:"foo"`
Bar int `json:"bar"`
}
func main() {
itemJson, _ := json.Marshal(&Item{})
fmt.Println(string(itemJson))
item := Item{}
emptyJson := "{}"
_ = json.Unmarshal([]byte(emptyJson), &item)
fmt.Println(item.Bar, item.Foo)
}
In the above example we are doing two things. First, we marshal en empty Item
struct without any values. Then we take an empty JSON object and Unmarshal this to a new Item
struct and then print the values of Bar
and Foo
. The following is the output we get from running the above code:
{"foo":false,"bar":0}
0 false
Even though we did not set any values, Bar
has a value of 0
and Foo
has a value of false
. This can be a problem with optional parameters because we don’t know if the data we received was actually set to a value or omitted. In the same way, we might end up sending values which we did not intend to send. Now, let’s try changing our struct to use pointers instead:
type Item struct {
Foo *bool `json:"foo"`
Bar *int `json:"bar"`
}
If we run the same code but only adding *
we get the following output:
{"foo":null,"bar":null}
<nil> <nil>
Now we have have nil
values since we did not set anything. This allows us to check if a parameter was actually set to a value. For example, we might use the following code to check the value of item.Foo
after unmarshalling.
if item.Foo == nil {
// Foo was not set
} else if *item.Foo {
// Foo was true
} else if !*item.Foo {
// Foo was false
}
Conclusion
I hope this post has helped cover the common issues with Go and JSON. If I missed anything or made a mistake please let me know in the comments. Thank you for reading!