69

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"`
}
Community
  • 1
  • 1
Zhe Hu
  • 3,777
  • 4
  • 32
  • 45
  • 3
    In case this isn't working for some people, the comma before omitempty in the struct tag is important. When I left it out, the field was always omitted, even if it was not-nill. – Lobsterman Sep 24 '19 at 00:22
  • 2
    Just in case anybody run into my issue... Don't place a space between the comma and omitempty. – joninx Oct 16 '20 at 07:02
  • Maybe this answer could help? https://stackoverflow.com/a/18088527/2271198 – Marcos Oct 07 '21 at 23:12

3 Answers3

65

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)
}
Amin Shojaei
  • 5,451
  • 2
  • 38
  • 46
Amit Kumar Gupta
  • 17,184
  • 7
  • 46
  • 64
  • 7
    I know this is an old post. But I have a question. If using a pointer is a workaround, what's the catch? – rrw Apr 12 '18 at 09:09
  • 2
    Why not always use pointers for field types that are structs? – sdfsdf Jan 03 '20 at 23:33
  • I know these are old comments, but "the catch" and "why not always use pointers" is because we then always have to check if the pointer is `nil`, otherwise we could panic the process by doing the nil pointer dereference when accessing nil struct field. `if g.A.Name != "" {}; if g.A.ID == 0 {}` would turn into `if g.A != nil { if g.A.Name != "" { } // Colors and ID check...}`. – Delicious Bacon Jul 13 '23 at 18:52
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

sepetrov
  • 21
  • 2
-20

Easy way

type <name> struct {
< varname > < vartype > \`json : -\`
}

Example :

type Boy struct {
name string \`json : -\`
}

this way on marshaling name will not get serialized.

YoungHobbit
  • 13,254
  • 9
  • 50
  • 73
  • 6
    at least 15 people downvoted this without explaining to us why –  Dec 27 '19 at 15:15
  • 7
    @JeshanBabooa, 1. `name` field not exported! First letter of exported fields must be capital. 2. `json:"-"` is effective when the field is exported. 3. and final word: if the field is exported `json:"-"` will omit it at all.... – Mamrezo Oct 03 '20 at 17:03