golang json marshal: how to omit empty nested struct

JsonGo

Json Problem Overview


go playground

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.

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZhe HuView Question on Stackoverflow
Solution 1 - JsonAmit Kumar GuptaView Answer on Stackoverflow
Solution 2 - JsonsepetrovView Answer on Stackoverflow
Solution 3 - JsonPurnank JainView Answer on Stackoverflow