protoc-gen-go-json icon indicating copy to clipboard operation
protoc-gen-go-json copied to clipboard

MarshalJSON not called in some cases

Open chkohner opened this issue 4 years ago • 1 comments

The generated MarshalJSON() implementation currently has a pointer receiver:

func (msg *Foo) MarshalJSON() ([]byte, error) {
   //...
}

Generally this is fine, as protobufs aren't value-copyable and are almost always used in a pointer context, but if it's not a value receiver, there are subtle places where the json.Marshaler function won't be called properly.

For example:


type EmbedFoo struct {
   Foo
   TimeToLive int `json:"ttl"`
}

func main() {
   foo := Foo{}
   _, _ = json.Marshal(foo)     // FAIL, Does not call MarshalJSON
   _, _ = json.Marshal(&foo)    // OK

   foo2 := EmbedFoo{}
   _, _ = json.Marshal(foo2)    // FAIL, Does not call MarshalJSON
   _, _ = json.Marshal(&foo2)   // FAIL, Does not call MarshalJSON
}

Changing the generated MarshalJSON to have value receiver fixes it (due to the pointer-following logic that json.Marshal() performs):

func (msg Foo) MarshalJSON() ([]byte, error) {
   // ...
}

This would also mirror its common uses within Go itself (including the example in the docs):

func (a *Animal) UnmarshalJSON(b []byte) error {
   // ...
}

func (a Animal) MarshalJSON() ([]byte, error) {
   // ...
}

Note: This only applies to MarshalJSON. UnmarshalJSON should continue to have the pointer receiver.

chkohner avatar Mar 19 '21 02:03 chkohner

@chkohner proto messages require a pointer receiver to implement proto.Message required by marshalling funcs: https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson#Marshal

emcfarlane avatar Sep 21 '23 10:09 emcfarlane