Golang / Go – How to marshal struct to null if it has no fields?

Issue

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

Solution

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"}}

Answered By – icza

Answer Checked By – Pedro (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.