golang json marshal: how to omit empty nested struct

Solution 1:

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.

playground link:

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:

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