I know this would probably seem a common question, but bare with me...
I have a struct with one of the fields type equal to that of another struct:
type Developer struct {
Name string `json:"name,omitempty"`
ProjectRef *Ref `json:"project,omitempty"`
}
type Ref struct {
ID string `json:"id,omitempty"`
}
In my implementation I cannot guarantee if there is or isn't going to be a ProjectRef for a Developer. If I create a Ref with a null ID, i.e. an empty string, then this field is omitted from the Ref, however, even though my Ref has no fields at this point, why is it not omitted from being empty?
I guess one way of overcoming this would be with a bunch of conditional statements, but I don't want to bring myself to doing that because I have a lot of cases where this functionality is desired.
Full demo code:
package main
import (
"encoding/json"
"fmt"
)
type Developer struct {
Name string `json:"name,omitempty"`
ProjectRef *Ref `json:"project,omitempty"`
}
type Ref struct {
ID string `json:"id,omitempty"`
}
func main() {
developer := &Developer{
Name: "Charlie",
ProjectRef: &Ref{ID: ""},
}
jsonBytes, err := json.Marshal(developer)
if err != nil {
panic(err)
}
fmt.Println(string(jsonBytes))
// {"name":"Charlie","project":{}}
}
Link to playground: https://go.dev/play/p/D2edbrACXY2
Thank you in advance
CodePudding user response:
Structs are marshaled even if all their fields hold the zero values.
One way is to leave the Developer.ProjectRef
field nil
, and remove the omitempty
attribute. That way it will be marshaled into the JSON null
value:
type Developer struct {
Name string `json:"name,omitempty"`
ProjectRef *Ref `json:"project"`
}
Testing it:
developer := &Developer{
Name: "Charlie",
}
jsonBytes, err := json.Marshal(developer)
if err != nil {
panic(err)
}
fmt.Println(string(jsonBytes))
developer.ProjectRef = &Ref{ID: "abc"}
jsonBytes, err = json.Marshal(developer)
if err != nil {
panic(err)
}
fmt.Println(string(jsonBytes))
Output (try it on the Go Playground):
{"name":"Charlie","project":null}
{"name":"Charlie","project":{"id":"abc"}}
If you always want Developer.ProjectRef
to be a non-nil
pointer, then write a custom JSON marshaler for Ref
, which could marshal the JSON null
value if the ID is empty:
func (r *Ref) MarshalJSON() ([]byte, error) {
if r.ID == "" {
return []byte("null"), nil
}
type ref2 Ref
return json.Marshal((*ref2)(r))
}
Testing it:
developer := &Developer{
Name: "Charlie",
ProjectRef: &Ref{ID: ""},
}
jsonBytes, err := json.Marshal(developer)
if err != nil {
panic(err)
}
fmt.Println(string(jsonBytes))
developer.ProjectRef.ID = "abc"
jsonBytes, err = json.Marshal(developer)
if err != nil {
panic(err)
}
fmt.Println(string(jsonBytes))
Output (try it on the Go Playground):
{"name":"Charlie","project":null}
{"name":"Charlie","project":{"id":"abc"}}