Golang learning journal — (4) Go marshal, unmarshal, and omitempty tag
Introduction
Hi there! I am Shelly, a non-CS background developer since 2020. This article belongs to my Golang learning journal series. For all the related articles, you can find the links in the table of contents at the very first article of this series.
Relationship
Before jumping into the definition of what is Unmarshal
, Marshal
, and omitempty
tag, let’s first understand that, in Go’s official documentation, omitempty
is mentioned only under Marshal
section.
Hence,
omitempty
only impactsMarshal
, but notUnmarshal
.
Definition
Go Marshal
Go Marshal describes the process of encoding
Go struct into JSON-encoded form (in []byte
).
We can use json.Marshal()
to replace json.NewEncoder() + encoder.Encode()
, below is how you can do it.
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
p := Person{Name: "Alice", Age: 30}
// 1. Using json.Marshal()
marshaled, _ := json.Marshal(p)
fmt.Println("Using json.Marshal():", string(marshaled))
// will print out: {"name":"Alice","age":30}
// 2. Using json.NewEncoder().Encode()
var buf bytes.Buffer
_ = json.NewEncoder(&buf).Encode(p)
fmt.Println("Using json.NewEncoder().Encode():", buf.String())
// will print out: {"name":"Alice","age":30}
}
Go Unmarshal
Go Unmarshal describes the process of decoding
JSON-encoded form (in []byte
) into Go-struct.
We can use json.Unmarshal()
to replace json.NewEncoder() + encoder.Encode()
, below is how you can do it.
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonBytes := []byte(`{"name": "Alice", "age": 30}`)
// 1. Using json.Unmarshal
var alice1 Person
_ = json.Unmarshal(jsonBytes, &alice1)
fmt.Println("Using json.Unmarshal():", alice1)
// will print out `Using json.Unmarshal(): {Alice 30}`
// 2. Using json.NewDecoder().Decode()
var alice2 Person
r := bytes.NewReader(jsonBytes)
_ = json.NewDecoder(r).Decode(&alice2)
fmt.Println("Using json.NewDecoder().Decode():", alice2)
// will print out `Using json.NewDecoder().Decode(): {Alice 30}`
}
Omitempty
The last topic is omitempty
! We can use omitempty
when doing Marshal
(note: rather thanUnmarshal
!). What is the feature of omitempty? Using omitempty
will ignore the field when the field’s value is zero value
.
Hence, when encode to JSON bytes, the field that has zero value
and is with omitempty
tag will be ignored.
What is zero value
? The zero-value for string is empty string ""
; for integer is 0
, …etc.
Note that zero value can come from :
- having a key but with zero value e.g.
{Age: 30}
, or - do not have a key at all e.g.
{}
Below code snippet will demonstrate the power of omitempty
.
package main
import (
"encoding/json"
"fmt"
)
type PersonWithOmitempty struct {
Age int `json:"age,omitempty"`
}
type PersonNoOmitempty struct {
Age int `json:"age"`
}
func main() {
aliceExplicit0 := `{"age":0}`
aliceImplicit0 := `{}`
var aliceExplicitNoAgeWithOmitempty *PersonWithOmitempty
_ = json.Unmarshal([]byte(aliceExplicit0), &aliceExplicitNoAgeWithOmitempty)
fmt.Printf("Unmarshal aliceExplicitNoAgeWithOmitempty: %#v\n", aliceExplicitNoAgeWithOmitempty)
var aliceImplicitNoAgeWithOmitempty *PersonWithOmitempty
_ = json.Unmarshal([]byte(aliceImplicit0), &aliceImplicitNoAgeWithOmitempty)
fmt.Printf("Unmarshal aliceImplicitNoAgeWithOmitempty: %#v\n", aliceImplicitNoAgeWithOmitempty)
var aliceExplicitNoAgeNoOmitempty *PersonNoOmitempty
_ = json.Unmarshal([]byte(aliceExplicit0), &aliceExplicitNoAgeNoOmitempty)
fmt.Printf("Unmarshal aliceExplicitNoAgeNoOmitempty: %#v\n", aliceExplicitNoAgeNoOmitempty)
var aliceImplicitNoAgeNoOmitempty *PersonNoOmitempty
_ = json.Unmarshal([]byte(aliceImplicit0), &aliceImplicitNoAgeNoOmitempty)
fmt.Printf("Unmarshal aliceImplicitNoAgeNoOmitempty: %#v\n", aliceImplicitNoAgeNoOmitempty)
// Marshal
aliceExplicitNoAgeWithOmitEmpty := PersonWithOmitempty{
Age: 0,
}
aliceImplicitNoAgeWithOmitEmpty := PersonWithOmitempty{}
aliceExplicitNoAgeNoOmitEmpty := PersonNoOmitempty{
Age: 0,
}
aliceImplicitNoAgeNoOmitEmpty := PersonNoOmitempty{}
b_aliceExplicitNoAgeWithOmitEmpty, _ := json.Marshal(aliceExplicitNoAgeWithOmitEmpty)
b_aliceimplicitNoAgeWithOmitEmpty, _ := json.Marshal(aliceImplicitNoAgeWithOmitEmpty)
b_aliceExplicitNoAgeNoOmitEmpty, _ := json.Marshal(aliceExplicitNoAgeNoOmitEmpty)
b_aliceimplicitNoAgeNoOmitEmpty, _ := json.Marshal(aliceImplicitNoAgeNoOmitEmpty)
fmt.Printf("Marshal %s%s\n", "aliceExplicitNoAgeWithOmitEmpty: ", string(b_aliceExplicitNoAgeWithOmitEmpty))
fmt.Printf("Marshal %s%s\n", "aliceImplicitNoAgeWithOmitEmpty: ", string(b_aliceimplicitNoAgeWithOmitEmpty))
fmt.Printf("Marshal %s%s\n", "aliceExplicitNoAgeNoOmitEmpty: ", string(b_aliceExplicitNoAgeNoOmitEmpty))
fmt.Printf("Marshal %s%s\n", "aliceImplicitNoAgeNoOmitEmpty: ", string(b_aliceimplicitNoAgeNoOmitEmpty))
}
The above code will print out:
Unmarshal aliceExplicitNoAgeWithOmitempty: &main.PersonWithOmitempty{Age:0}
Unmarshal aliceImplicitNoAgeWithOmitempty: &main.PersonWithOmitempty{Age:0}
Unmarshal aliceExplicitNoAgeNoOmitempty: &main.PersonNoOmitempty{Age:0}
Unmarshal aliceImplicitNoAgeNoOmitempty: &main.PersonNoOmitempty{Age:0}
Marshal aliceExplicitNoAgeWithOmitEmpty: {}
Marshal aliceImplicitNoAgeWithOmitEmpty: {}
Marshal aliceExplicitNoAgeNoOmitEmpty: {"age":0}
Marshal aliceImplicitNoAgeNoOmitEmpty: {"age":0}
Note that, for Unmarshal
, 4 results are the same, because omitempty
only works together with Marshal
, not Unmarshal
.
However, for Marshal
, 4 results are NOT the same. The two thats with omitempty
tag omits the field “age” completely, whereas the last two that do not haveomitempty
tag still show the key “age”.
Caveat when using it with zerolog
zerolog’s Interface() actually is doing Marshal
, according to the documentation:
Interface adds the field key with i marshaled using reflection.
Hence, if you try to use zerolog’s log.Info().Interface()
to print out the value of a key, the key will disappear when it has omitempty
key.
- Without
omitempty
key, the keyrevenue
will still show up in zerolog:
type Request struct {
Revenue int `json:"revenue"`
}
var Request Request
dec := json.NewDecoder(r.Body)
err := dec.Decode(&Request)
log.Info().Interface("Request", Request).Msg("Request") // zerolog
// "Request":{"revenue":0} // here omitempty plays a role, because zerolog use "Marshal". However "revenue" does not disappear because there is no "omitempty" tag
fmt.Printf("Unmarshal: %#v\n", Request)
// "Request":{"revenue":0} // here omitempty does NOT play a role. With Unmarshal/Decode, all keys are always preserved.
- With
omitempty
key, the keyrevenue
willNOT
show up in zerolog:
type Request struct {
Revenue int `json:"revenue,omitempty"`
}
var Request Request
dec := json.NewDecoder(r.Body)
err := dec.Decode(&Request)
log.Info().Interface("Request", Request).Msg("Request") // zerolog
// "Request":{} // here omitempty plays a role, because zerolog use "Marshal".
fmt.Printf("Unmarshal: %#v\n", Request)
// "Request":{"revenue":0} // here omitempty does NOT play a role. With Unmarshal/Decode, all keys are always preserved.
Now, I hope this can help you decide when to use omitempty.
See you next time. :)
Thanks for reading till here. If you found the article helpful, feel free to clap multiple times on the article (one person can clap up to 50 times!), or ❤️ give me some tips following the below link ❤️. Thank you in advance :)