MemReflect
MemReflect

Reputation: 551

Misunderstanding how to use MarshalJSON

I have a rather unusual situation. I want MarshalJSON to conditionally omit a struct field. In the example below, the idea is to omit output of the B Bool field if B.Value == B.Undefined.

type Bool struct {
    // Current value
    Value bool
    // Value if undefined
    Undefined bool
}

func (b Bool) MarshalJSON() ([]byte, error) {
    if b.Value == b.Undefined {
        return []byte{}, nil
    } else if b.Value {
        return ([]byte)("true"), nil
    }
    return ([]byte)("false"), nil
}

func main() {
    var example = struct {
        N int  `json:"foo"`
        B Bool `json:"value,omitempty"`
    }
    example.B = Bool{true, true}
    output, err := json.Marshal(example)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(output))
}

Playground link

According to the docs:

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

I return an empty byte slice, which results in an error:

panic: json: error calling MarshalJSON for type main.Bool: unexpected end of JSON input

goroutine 1 [running]:
main.main()
    /tmp/sandbox933539113/prog.go:32 +0x160

If I set example.B = Bool{false, true}, then it prints the result, but the field still isn't omitted, despite returning "false" from Bool.MarshalJSON. What am I doing wrong? Does a value with a type satisfying the Marshaler interface effectively ignore the omitempty tag entry?

Upvotes: 0

Views: 903

Answers (1)

Jonathan Hall
Jonathan Hall

Reputation: 79674

 if b.Value == b.Undefined {
        return []byte{}, nil

produces invalid JSON: it returns 0 bytes. Your MarshalJSON method must return valid JSON, which means it must return something.

If your goal is to omit that field entirely, when it's not set, use a pointer, and don't set it:

func main() {
    var example = struct {
        N int   `json:"foo"`
        B *Bool `json:"value,omitempty"`
    }
    example.B = Bool{N: true, B: nil}
    output, err := json.Marshal(example)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(output))
}

If you want to omit that field entirely when the value of Bool is a particular value, then you must define your MarshalJSON method on the containing struct, so that it can properly omit the field when desired.

Upvotes: 5

Related Questions