Golang learning journal — (4) Go marshal, unmarshal, and omitempty tag

How to use omitempty struct tag when marshaling

Shelly
4 min readJul 25, 2023
Photo by Kier in Sight Archives on Unsplash

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

JSON Unmarshal, Marshal, Omitempty

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 impacts Marshal, but not Unmarshal.

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 omitemptywhen 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.

JSON Unmarshal, Marshal, Omitempty

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 :

  1. having a key but with zero value e.g. {Age: 30}, or
  2. 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 key revenue 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 key revenue will NOT 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 :)

--

--