136

I have a struct like this:

type Result struct {
    Data       MyStruct  `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

But even if the instance of MyStruct is entirely empty (meaning, all values are default), it's being serialized as:

"data":{}

I know that the encoding/json docs specify that "empty" fields are:

false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero

but with no consideration for a struct with all empty/default values. All of its fields are also tagged with omitempty, but this has no effect.

How can I get the JSON package to not marshal my field that is an empty struct?

Matt
  • 22,721
  • 17
  • 71
  • 112

5 Answers5

207

As the docs say, "any nil pointer." -- make the struct a pointer. Pointers have obvious "empty" values: nil.

Fix - define the type with a struct pointer field:

type Result struct {
    Data       *MyStruct `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

Then a value like this:

result := Result{}

Will marshal as:

{}

Explanation: Notice the *MyStruct in our type definition. JSON serialization doesn't care whether it is a pointer or not -- that's a runtime detail. So making struct fields into pointers only has implications for compiling and runtime).

Just note that if you do change the field type from MyStruct to *MyStruct, you will need pointers to struct values to populate it, like so:

Data: &MyStruct{ /* values */ }
Matt
  • 22,721
  • 17
  • 71
  • 112
  • 3
    Bless you Matt, This is what I was looking for – Venkata S S K M Chaitanya Dec 20 '19 at 16:27
  • 1
    @Matt, are you sure that `&MyStruct{ /* values */ }` counts as a nil pointer? The value is not nil. – Shuzheng Feb 09 '20 at 11:41
  • @Matt Is it possible to make this default behaviour? I want to omitempty always. (basically not use the tag in each and every field of all structs) – Mohit Singh Feb 23 '20 at 05:26
  • Hi Matt, if the `Data` field is not empty, it would be memory address gibberish that marshalled into byte stream rather than the data itself, which is not intended in most cases? – mzoz Mar 09 '21 at 05:31
23

As @chakrit mentioned in a comment, you can't get this to work by implementing json.Marshaler on MyStruct, and implementing a custom JSON marshalling function on every struct that uses it can be a lot more work. It really depends on your use case as to whether it's worth the extra work or whether you're prepared to live with empty structs in your JSON, but here's the pattern I use applied to Result:

type Result struct {
    Data       MyStruct
    Status     string   
    Reason     string    
}

func (r Result) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    }{
        Data:   &r.Data,
        Status: r.Status,
        Reason: r.Reason,
    })        
}

func (r *Result) UnmarshalJSON(b []byte) error {
    decoded := new(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    })
    err := json.Unmarshal(b, decoded)
    if err == nil {
        r.Data = decoded.Data
        r.Status = decoded.Status
        r.Reason = decoded.Reason
    }
    return err
}

If you have huge structs with many fields this can become tedious, especially changing a struct's implementation later, but short of rewriting the whole json package to suit your needs (not a good idea), this is pretty much the only way I can think of getting this done while still keeping a non-pointer MyStruct in there.

Also, you don't have to use inline structs, you can create named ones. I use LiteIDE with code completion though, so I prefer inline to avoid clutter.

Leylandski
  • 421
  • 5
  • 8
  • 1
    This answer makes no sense. It solves nothing. `&r.Data` is never `nil`, so it will never be omited. – super Jul 22 '21 at 08:26
9

Data is an initialized struct, so it isn't considered empty because encoding/json only looks at the immediate value, not the fields inside the struct.

Unfortunately, returning nil from json.Marshaler doesn't currently work:

func (_ MyStruct) MarshalJSON() ([]byte, error) {
    if empty {
        return nil, nil // unexpected end of JSON input
    }
    // ...
}

You could give Result a marshaler as well, but it's not worth the effort.

The only option, as Matt suggests, is to make Data a pointer and set the value to nil.

mirrorkeydev
  • 68
  • 1
  • 6
Luke
  • 13,678
  • 7
  • 45
  • 79
  • 2
    I don't see why `encoding/json` **can not** check the struct's child fields. It wouldn't be very efficient, yes. But it's certainly not impossible. – nemo Aug 06 '13 at 20:23
  • @nemo I see your point, I changed the wording. It doesn't do it because it wouldn't be efficient. It can be done with `json.Marshaler` on a case-by-case basis though. – Luke Aug 06 '13 at 20:48
  • 3
    It is **not** possible to _decide wether or not `MyStruct` is empty_ by implementing a `json.Marshaler` on `MyStruct` itself. Proof: http://play.golang.org/p/UEC8A3JGvx – chakrit Dec 27 '14 at 16:10
  • To do that you'd have to implement `json.Marshaler` on the containing `Result` type itself which could be very inconvenient. – chakrit Dec 27 '14 at 16:12
6

There is an outstanding Golang proposal for this feature which has been active for over 4 years, so at this point, it is safe to assume that it will not make it into the standard library anytime soon. As @Matt pointed out, the traditional approach is to convert the structs to pointers-to-structs. If this approach is infeasible (or impractical), then an alternative is to use an alternate json encoder which does support omitting zero value structs.

I created a mirror of the Golang json library (clarketm/json) with added support for omitting zero value structs when the omitempty tag is applied. This library detects zeroness in a similar manner to the popular YAML encoder go-yaml by recursively checking the public struct fields.

e.g.

$ go get -u "github.com/clarketm/json"
import (
    "fmt"
    "github.com/clarketm/json" // drop-in replacement for `encoding/json`
)

type Result struct {
    Data   MyStruct `json:"data,omitempty"`
    Status string   `json:"status,omitempty"`
    Reason string   `json:"reason,omitempty"`
}

j, _ := json.Marshal(&Result{
    Status: "204",
    Reason: "No Content",
})

fmt.Println(string(j))
// Note: `data` is omitted from the resultant json.
{
  "status": "204"
  "reason": "No Content"
}
Travis Clarke
  • 5,951
  • 6
  • 29
  • 36
0

omitempty is not only the solution if you want to omit it then you have replace code from

type Result struct {
    Data       MyStruct  `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

To this,

type Result struct {
    Data       *MyStruct  `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

Now omitempty will work properly Thanks...

Additionally, suppose if you want to keep condition like oneOf means only one field should be allowed to fill in stuct then the code would looks like,

type Result struct {
    Data1      *MyStruct1    `json:"Data1,omitempty"`
    Data2      *Mystruct2    `json:"Data2,omitempty"`
    Data3      *Mystruct3    `json:"Data3,omitempty"`
}

The above code only allow you to fill only 1 field.
E.g if you are filling Data1 then you can't fill Data2 & Data3, it will throw error for that