golang json marshal: how to omit empty nested struct
JsonGoJson Problem Overview
As shown in the code above, one can use json:",omitempty"
to omit certain fields in a struct to appear in json.
For example
type ColorGroup struct {
ID int `json:",omitempty"`
Name string
Colors []string
}
type Total struct {
A ColorGroup`json:",omitempty"`
B string`json:",omitempty"`
}
group := Total{
A: ColorGroup{},
}
In this case, B
won't show up in json.Marshal(group)
However, if
group := Total{
B:"abc",
}
A
still shows up in json.Marshal(group)
{"A":{"Name":"","Colors":null},"B":"abc"}
Question is how do we get only
{"B":"abc"}
EDIT:
After some googling, here is a suggestion use pointer, in other words, turn Total
into
type Total struct {
A *ColorGroup`json:",omitempty"`
B string`json:",omitempty"`
}
Json Solutions
Solution 1 - Json
From the documentation:
>Struct values encode as JSON objects. Each exported struct field becomes a member of the object unless > >- the field's tag is "-", or >- the field is empty and its tag specifies the "omitempty" option. > >The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero.
In your declaration of group
, it's implicit that group.A
will be the zero value of the ColorGroup
struct type. And notice that zero-values-of-struct-types is not mentioned in that list of things that are considered "empty values".
As you found, the workaround for your case is to use a pointer. This will work if you don't specify A
in your declaration of group
. If you specify it to be a pointer to a zero-struct, then it will show up again.
package main
import (
"encoding/json"
"fmt"
"os"
)
func main() {
type colorGroup struct {
ID int `json:",omitempty"`
Name string
Colors []string
}
type total struct {
A *colorGroup `json:",omitempty"`
B string `json:",omitempty"`
}
groupWithNilA := total{
B: "abc",
}
b, err := json.Marshal(groupWithNilA)
if err != nil {
fmt.Println("error:", err)
}
os.Stderr.Write(b)
println()
groupWithPointerToZeroA := total{
A: &colorGroup{},
B: "abc",
}
b, err = json.Marshal(groupWithPointerToZeroA)
if err != nil {
fmt.Println("error:", err)
}
os.Stderr.Write(b)
}
Solution 2 - Json
This is an alternative solution, in case you would like to avoid using pointers to structs. The Container
struct implements json.Marshaller
, which allows us to decide which members of the strcut should be omitted.
https://play.golang.com/p/hMJbQ-QQ5PU
package main
import (
"encoding/json"
"fmt"
)
func main() {
for _, c := range []Container{
{},
{
Element: KeyValue{
Key: "foo",
Value: "bar",
},
},
} {
b, err := json.Marshal(c)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}
}
type Container struct {
Element KeyValue
}
func (c Container) MarshalJSON() ([]byte, error) {
// Alias is an alias type of Container to avoid recursion.
type Alias Container
// AliasWithInterface wraps Alias and overrides the struct members,
// which we want to omit if they are the zero value of the type.
type AliasWithInterface struct {
Alias
Element interface{} `json:",omitempty"`
}
return json.Marshal(AliasWithInterface{
Alias: Alias(c),
Element: c.Element.jsonValue(),
})
}
type KeyValue struct {
Key string
Value string
}
// jsonValue returns nil if kv is the zero value of KeyValue. It returns kv otherwise.
func (kv KeyValue) jsonValue() interface{} {
var zero KeyValue
if kv == zero {
return nil
}
return kv
}
EDIT: added documentation
Solution 3 - Json
Easy way
type <name> struct {
< varname > < vartype > \`json : -\`
}
Example :
type Boy struct {
name string \`json : -\`
}
this way on marshaling name
will not get serialized.