Skip to content

Commit a100a47

Browse files
Copilotasim
andauthored
Fix google.protobuf.Any JSON marshaling missing @type field (#2845)
* Initial plan * Update JSON codec to use modern protojson for proper Any type support Co-authored-by: asim <17530+asim@users.noreply.github.com> * Add comprehensive tests for google.protobuf.Any JSON marshaling Co-authored-by: asim <17530+asim@users.noreply.github.com> * Revert codec/proto to old protobuf package for backward compatibility Co-authored-by: asim <17530+asim@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: asim <17530+asim@users.noreply.github.com>
1 parent 4ba40ea commit a100a47

File tree

5 files changed

+220
-29
lines changed

5 files changed

+220
-29
lines changed

codec/json/any_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package json
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"google.golang.org/protobuf/types/known/anypb"
8+
"google.golang.org/protobuf/types/known/wrapperspb"
9+
)
10+
11+
// TestAnyTypeMarshaling tests that google.protobuf.Any types are properly marshaled with @type field
12+
func TestAnyTypeMarshaling(t *testing.T) {
13+
marshaler := Marshaler{}
14+
15+
// Create a StringValue message
16+
stringValue := wrapperspb.String("test value")
17+
18+
// Wrap it in an Any message
19+
anyMsg, err := anypb.New(stringValue)
20+
if err != nil {
21+
t.Fatalf("Failed to create Any message: %v", err)
22+
}
23+
24+
// Marshal using our JSON marshaler
25+
data, err := marshaler.Marshal(anyMsg)
26+
if err != nil {
27+
t.Fatalf("Failed to marshal Any message: %v", err)
28+
}
29+
30+
// Unmarshal into a map to check for @type field
31+
var result map[string]interface{}
32+
if err := json.Unmarshal(data, &result); err != nil {
33+
t.Fatalf("Failed to unmarshal JSON: %v", err)
34+
}
35+
36+
// Check that @type field exists
37+
typeURL, ok := result["@type"].(string)
38+
if !ok {
39+
t.Fatalf("@type field not found in JSON output. Got: %v", string(data))
40+
}
41+
42+
// Verify the type URL is correct
43+
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
44+
if typeURL != expectedTypeURL {
45+
t.Errorf("Expected @type to be %s, got %s", expectedTypeURL, typeURL)
46+
}
47+
48+
// Verify the value field exists
49+
if _, ok := result["value"]; !ok {
50+
t.Errorf("value field not found in JSON output. Got: %v", string(data))
51+
}
52+
53+
t.Logf("Successfully marshaled Any type with @type field: %s", string(data))
54+
}
55+
56+
// TestAnyTypeUnmarshaling tests that JSON with @type field can be unmarshaled into google.protobuf.Any
57+
func TestAnyTypeUnmarshaling(t *testing.T) {
58+
marshaler := Marshaler{}
59+
60+
// JSON representation of an Any message with @type field
61+
jsonData := []byte(`{
62+
"@type": "type.googleapis.com/google.protobuf.StringValue",
63+
"value": "test value"
64+
}`)
65+
66+
// Unmarshal into an Any message
67+
anyMsg := &anypb.Any{}
68+
if err := marshaler.Unmarshal(jsonData, anyMsg); err != nil {
69+
t.Fatalf("Failed to unmarshal Any message: %v", err)
70+
}
71+
72+
// Verify the type URL is set
73+
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
74+
if anyMsg.TypeUrl != expectedTypeURL {
75+
t.Errorf("Expected TypeUrl to be %s, got %s", expectedTypeURL, anyMsg.TypeUrl)
76+
}
77+
78+
// Unmarshal the contained message
79+
stringValue := &wrapperspb.StringValue{}
80+
if err := anyMsg.UnmarshalTo(stringValue); err != nil {
81+
t.Fatalf("Failed to unmarshal contained message: %v", err)
82+
}
83+
84+
// Verify the value
85+
expectedValue := "test value"
86+
if stringValue.Value != expectedValue {
87+
t.Errorf("Expected value to be %s, got %s", expectedValue, stringValue.Value)
88+
}
89+
90+
t.Logf("Successfully unmarshaled Any type from JSON with @type field")
91+
}

codec/json/codec_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package json
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"testing"
7+
8+
"go-micro.dev/v5/codec"
9+
"google.golang.org/protobuf/types/known/anypb"
10+
"google.golang.org/protobuf/types/known/wrapperspb"
11+
)
12+
13+
// mockReadWriteCloser implements io.ReadWriteCloser for testing
14+
type mockReadWriteCloser struct {
15+
*bytes.Buffer
16+
}
17+
18+
func (m *mockReadWriteCloser) Close() error {
19+
return nil
20+
}
21+
22+
// TestCodecAnyTypeWrite tests that google.protobuf.Any types are properly written with @type field
23+
func TestCodecAnyTypeWrite(t *testing.T) {
24+
buf := &mockReadWriteCloser{Buffer: bytes.NewBuffer(nil)}
25+
c := NewCodec(buf).(*Codec)
26+
27+
// Create a StringValue message
28+
stringValue := wrapperspb.String("test value")
29+
30+
// Wrap it in an Any message
31+
anyMsg, err := anypb.New(stringValue)
32+
if err != nil {
33+
t.Fatalf("Failed to create Any message: %v", err)
34+
}
35+
36+
// Write the message
37+
msg := &codec.Message{
38+
Type: codec.Response,
39+
}
40+
if err := c.Write(msg, anyMsg); err != nil {
41+
t.Fatalf("Failed to write Any message: %v", err)
42+
}
43+
44+
// Parse the written JSON
45+
var result map[string]interface{}
46+
if err := json.Unmarshal(buf.Bytes(), &result); err != nil {
47+
t.Fatalf("Failed to unmarshal JSON: %v", err)
48+
}
49+
50+
// Check that @type field exists
51+
typeURL, ok := result["@type"].(string)
52+
if !ok {
53+
t.Fatalf("@type field not found in JSON output. Got: %v", buf.String())
54+
}
55+
56+
// Verify the type URL is correct
57+
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
58+
if typeURL != expectedTypeURL {
59+
t.Errorf("Expected @type to be %s, got %s", expectedTypeURL, typeURL)
60+
}
61+
62+
t.Logf("Successfully wrote Any type with @type field: %s", buf.String())
63+
}
64+
65+
// TestCodecAnyTypeRead tests that JSON with @type field can be read into google.protobuf.Any
66+
func TestCodecAnyTypeRead(t *testing.T) {
67+
// JSON representation of an Any message with @type field
68+
jsonData := `{"@type":"type.googleapis.com/google.protobuf.StringValue","value":"test value"}`
69+
70+
buf := &mockReadWriteCloser{Buffer: bytes.NewBufferString(jsonData + "\n")}
71+
c := NewCodec(buf).(*Codec)
72+
73+
// Read into an Any message
74+
anyMsg := &anypb.Any{}
75+
if err := c.ReadBody(anyMsg); err != nil {
76+
t.Fatalf("Failed to read Any message: %v", err)
77+
}
78+
79+
// Verify the type URL is set
80+
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
81+
if anyMsg.TypeUrl != expectedTypeURL {
82+
t.Errorf("Expected TypeUrl to be %s, got %s", expectedTypeURL, anyMsg.TypeUrl)
83+
}
84+
85+
// Unmarshal the contained message
86+
stringValue := &wrapperspb.StringValue{}
87+
if err := anyMsg.UnmarshalTo(stringValue); err != nil {
88+
t.Fatalf("Failed to unmarshal contained message: %v", err)
89+
}
90+
91+
// Verify the value
92+
expectedValue := "test value"
93+
if stringValue.Value != expectedValue {
94+
t.Errorf("Expected value to be %s, got %s", expectedValue, stringValue.Value)
95+
}
96+
97+
t.Logf("Successfully read Any type from JSON with @type field")
98+
}

codec/json/json.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import (
55
"encoding/json"
66
"io"
77

8-
"github.com/golang/protobuf/jsonpb"
9-
"github.com/golang/protobuf/proto"
108
"go-micro.dev/v5/codec"
9+
"google.golang.org/protobuf/encoding/protojson"
10+
"google.golang.org/protobuf/proto"
1111
)
1212

1313
type Codec struct {
@@ -25,7 +25,12 @@ func (c *Codec) ReadBody(b interface{}) error {
2525
return nil
2626
}
2727
if pb, ok := b.(proto.Message); ok {
28-
return jsonpb.UnmarshalNext(c.Decoder, pb)
28+
// Read all JSON data from decoder
29+
var raw json.RawMessage
30+
if err := c.Decoder.Decode(&raw); err != nil {
31+
return err
32+
}
33+
return protojson.Unmarshal(raw, pb)
2934
}
3035
return c.Decoder.Decode(b)
3136
}
@@ -34,6 +39,15 @@ func (c *Codec) Write(m *codec.Message, b interface{}) error {
3439
if b == nil {
3540
return nil
3641
}
42+
if pb, ok := b.(proto.Message); ok {
43+
data, err := protojson.Marshal(pb)
44+
if err != nil {
45+
return err
46+
}
47+
// Write the marshaled data to the encoder
48+
var raw json.RawMessage = data
49+
return c.Encoder.Encode(raw)
50+
}
3751
return c.Encoder.Encode(b)
3852
}
3953

codec/json/marshaler.go

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,28 @@
11
package json
22

33
import (
4-
"bytes"
54
"encoding/json"
65

7-
"github.com/golang/protobuf/jsonpb"
8-
"github.com/golang/protobuf/proto"
9-
"github.com/oxtoacart/bpool"
6+
"google.golang.org/protobuf/encoding/protojson"
7+
"google.golang.org/protobuf/proto"
108
)
119

12-
var jsonpbMarshaler = &jsonpb.Marshaler{}
13-
14-
// create buffer pool with 16 instances each preallocated with 256 bytes.
15-
var bufferPool = bpool.NewSizedBufferPool(16, 256)
10+
var protojsonMarshaler = protojson.MarshalOptions{
11+
EmitUnpopulated: false,
12+
}
1613

1714
type Marshaler struct{}
1815

1916
func (j Marshaler) Marshal(v interface{}) ([]byte, error) {
2017
if pb, ok := v.(proto.Message); ok {
21-
buf := bufferPool.Get()
22-
defer bufferPool.Put(buf)
23-
if err := jsonpbMarshaler.Marshal(buf, pb); err != nil {
24-
return nil, err
25-
}
26-
return buf.Bytes(), nil
18+
return protojsonMarshaler.Marshal(pb)
2719
}
2820
return json.Marshal(v)
2921
}
3022

3123
func (j Marshaler) Unmarshal(d []byte, v interface{}) error {
3224
if pb, ok := v.(proto.Message); ok {
33-
return jsonpb.Unmarshal(bytes.NewReader(d), pb)
25+
return protojson.Unmarshal(d, pb)
3426
}
3527
return json.Unmarshal(d, v)
3628
}

server/grpc/codec.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,23 @@ import (
44
"encoding/json"
55
"strings"
66

7-
b "bytes"
8-
9-
"github.com/golang/protobuf/jsonpb"
10-
"github.com/golang/protobuf/proto"
117
"go-micro.dev/v5/codec"
128
"go-micro.dev/v5/codec/bytes"
139
"google.golang.org/grpc"
1410
"google.golang.org/grpc/encoding"
1511
"google.golang.org/grpc/metadata"
12+
"google.golang.org/protobuf/encoding/protojson"
13+
"google.golang.org/protobuf/proto"
1614
)
1715

1816
type jsonCodec struct{}
1917
type bytesCodec struct{}
2018
type protoCodec struct{}
2119
type wrapCodec struct{ encoding.Codec }
2220

23-
var jsonpbMarshaler = &jsonpb.Marshaler{
24-
EnumsAsInts: false,
25-
EmitDefaults: false,
26-
OrigName: true,
21+
var protojsonMarshaler = protojson.MarshalOptions{
22+
UseProtoNames: true,
23+
EmitUnpopulated: false,
2724
}
2825

2926
var (
@@ -85,8 +82,7 @@ func (protoCodec) Name() string {
8582

8683
func (jsonCodec) Marshal(v interface{}) ([]byte, error) {
8784
if pb, ok := v.(proto.Message); ok {
88-
s, err := jsonpbMarshaler.MarshalToString(pb)
89-
return []byte(s), err
85+
return protojsonMarshaler.Marshal(pb)
9086
}
9187

9288
return json.Marshal(v)
@@ -97,7 +93,7 @@ func (jsonCodec) Unmarshal(data []byte, v interface{}) error {
9793
return nil
9894
}
9995
if pb, ok := v.(proto.Message); ok {
100-
return jsonpb.Unmarshal(b.NewReader(data), pb)
96+
return protojson.Unmarshal(data, pb)
10197
}
10298
return json.Unmarshal(data, v)
10399
}

0 commit comments

Comments
 (0)