3

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

0

1 Answer 1

4

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"}} 
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much, as an extension to this question, how do I write a custom marshaller so that if my field was an array of Refs and had no values, the overall array would result as null and not [null]. go.dev/play/p/7Q-NKfKfRk8
I prefer the custom marshaller so I don't have to have conditional statements when instantiating my Developer struct
@CharlieClarke Then you can define a type for []*Ref, and implement custom marshaling on it. And use this type in the struct of course. The custom logic could count the non-null refs, and if it's 0, marshal as the JSON null. See go.dev/play/p/Kk546M5QJ8Q
@CharlieClarke An improved version could just look for the first non-null ID to know if "normal" marshaling is needed, see here: go.dev/play/p/oqItnpQ_pQn

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.