如何不在Go中将空结构编组为JSON?

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?

Oh! Easy fix: "any nil pointer." -- make the struct a pointer.

Fix:

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

Notice the *MyStruct -- when I create a MyStruct now, I simply do so by reference:

myStruct := &MyStruct{ /* values */ }

And now the "empty" MyStruct is no longer marshaled into JSON as desired.

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.Marhsler currently doesn't 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.

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.