Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions codec/json/any_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package json

import (
"encoding/json"
"testing"

"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/wrapperspb"
)

// TestAnyTypeMarshaling tests that google.protobuf.Any types are properly marshaled with @type field
func TestAnyTypeMarshaling(t *testing.T) {
marshaler := Marshaler{}

// Create a StringValue message
stringValue := wrapperspb.String("test value")

// Wrap it in an Any message
anyMsg, err := anypb.New(stringValue)
if err != nil {
t.Fatalf("Failed to create Any message: %v", err)
}

// Marshal using our JSON marshaler
data, err := marshaler.Marshal(anyMsg)
if err != nil {
t.Fatalf("Failed to marshal Any message: %v", err)
}

// Unmarshal into a map to check for @type field
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}

// Check that @type field exists
typeURL, ok := result["@type"].(string)
if !ok {
t.Fatalf("@type field not found in JSON output. Got: %v", string(data))
}

// Verify the type URL is correct
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
if typeURL != expectedTypeURL {
t.Errorf("Expected @type to be %s, got %s", expectedTypeURL, typeURL)
}

// Verify the value field exists
if _, ok := result["value"]; !ok {
t.Errorf("value field not found in JSON output. Got: %v", string(data))
}

t.Logf("Successfully marshaled Any type with @type field: %s", string(data))
}

// TestAnyTypeUnmarshaling tests that JSON with @type field can be unmarshaled into google.protobuf.Any
func TestAnyTypeUnmarshaling(t *testing.T) {
marshaler := Marshaler{}

// JSON representation of an Any message with @type field
jsonData := []byte(`{
"@type": "type.googleapis.com/google.protobuf.StringValue",
"value": "test value"
}`)

// Unmarshal into an Any message
anyMsg := &anypb.Any{}
if err := marshaler.Unmarshal(jsonData, anyMsg); err != nil {
t.Fatalf("Failed to unmarshal Any message: %v", err)
}

// Verify the type URL is set
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
if anyMsg.TypeUrl != expectedTypeURL {
t.Errorf("Expected TypeUrl to be %s, got %s", expectedTypeURL, anyMsg.TypeUrl)
}

// Unmarshal the contained message
stringValue := &wrapperspb.StringValue{}
if err := anyMsg.UnmarshalTo(stringValue); err != nil {
t.Fatalf("Failed to unmarshal contained message: %v", err)
}

// Verify the value
expectedValue := "test value"
if stringValue.Value != expectedValue {
t.Errorf("Expected value to be %s, got %s", expectedValue, stringValue.Value)
}

t.Logf("Successfully unmarshaled Any type from JSON with @type field")
}
98 changes: 98 additions & 0 deletions codec/json/codec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package json

import (
"bytes"
"encoding/json"
"testing"

"go-micro.dev/v5/codec"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/wrapperspb"
)

// mockReadWriteCloser implements io.ReadWriteCloser for testing
type mockReadWriteCloser struct {
*bytes.Buffer
}

func (m *mockReadWriteCloser) Close() error {
return nil
}

// TestCodecAnyTypeWrite tests that google.protobuf.Any types are properly written with @type field
func TestCodecAnyTypeWrite(t *testing.T) {
buf := &mockReadWriteCloser{Buffer: bytes.NewBuffer(nil)}
c := NewCodec(buf).(*Codec)

// Create a StringValue message
stringValue := wrapperspb.String("test value")

// Wrap it in an Any message
anyMsg, err := anypb.New(stringValue)
if err != nil {
t.Fatalf("Failed to create Any message: %v", err)
}

// Write the message
msg := &codec.Message{
Type: codec.Response,
}
if err := c.Write(msg, anyMsg); err != nil {
t.Fatalf("Failed to write Any message: %v", err)
}

// Parse the written JSON
var result map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &result); err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}

// Check that @type field exists
typeURL, ok := result["@type"].(string)
if !ok {
t.Fatalf("@type field not found in JSON output. Got: %v", buf.String())
}

// Verify the type URL is correct
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
if typeURL != expectedTypeURL {
t.Errorf("Expected @type to be %s, got %s", expectedTypeURL, typeURL)
}

t.Logf("Successfully wrote Any type with @type field: %s", buf.String())
}

// TestCodecAnyTypeRead tests that JSON with @type field can be read into google.protobuf.Any
func TestCodecAnyTypeRead(t *testing.T) {
// JSON representation of an Any message with @type field
jsonData := `{"@type":"type.googleapis.com/google.protobuf.StringValue","value":"test value"}`

buf := &mockReadWriteCloser{Buffer: bytes.NewBufferString(jsonData + "\n")}
c := NewCodec(buf).(*Codec)

// Read into an Any message
anyMsg := &anypb.Any{}
if err := c.ReadBody(anyMsg); err != nil {
t.Fatalf("Failed to read Any message: %v", err)
}

// Verify the type URL is set
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
if anyMsg.TypeUrl != expectedTypeURL {
t.Errorf("Expected TypeUrl to be %s, got %s", expectedTypeURL, anyMsg.TypeUrl)
}

// Unmarshal the contained message
stringValue := &wrapperspb.StringValue{}
if err := anyMsg.UnmarshalTo(stringValue); err != nil {
t.Fatalf("Failed to unmarshal contained message: %v", err)
}

// Verify the value
expectedValue := "test value"
if stringValue.Value != expectedValue {
t.Errorf("Expected value to be %s, got %s", expectedValue, stringValue.Value)
}

t.Logf("Successfully read Any type from JSON with @type field")
}
20 changes: 17 additions & 3 deletions codec/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
"encoding/json"
"io"

"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"go-micro.dev/v5/codec"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)

type Codec struct {
Expand All @@ -25,7 +25,12 @@ func (c *Codec) ReadBody(b interface{}) error {
return nil
}
if pb, ok := b.(proto.Message); ok {
return jsonpb.UnmarshalNext(c.Decoder, pb)
// Read all JSON data from decoder
var raw json.RawMessage
if err := c.Decoder.Decode(&raw); err != nil {
return err
}
return protojson.Unmarshal(raw, pb)
}
return c.Decoder.Decode(b)
}
Expand All @@ -34,6 +39,15 @@ func (c *Codec) Write(m *codec.Message, b interface{}) error {
if b == nil {
return nil
}
if pb, ok := b.(proto.Message); ok {
data, err := protojson.Marshal(pb)
if err != nil {
return err
}
// Write the marshaled data to the encoder
var raw json.RawMessage = data
return c.Encoder.Encode(raw)
}
return c.Encoder.Encode(b)
}

Expand Down
22 changes: 7 additions & 15 deletions codec/json/marshaler.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,28 @@
package json

import (
"bytes"
"encoding/json"

"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/oxtoacart/bpool"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)

var jsonpbMarshaler = &jsonpb.Marshaler{}

// create buffer pool with 16 instances each preallocated with 256 bytes.
var bufferPool = bpool.NewSizedBufferPool(16, 256)
var protojsonMarshaler = protojson.MarshalOptions{
EmitUnpopulated: false,
}

type Marshaler struct{}

func (j Marshaler) Marshal(v interface{}) ([]byte, error) {
if pb, ok := v.(proto.Message); ok {
buf := bufferPool.Get()
defer bufferPool.Put(buf)
if err := jsonpbMarshaler.Marshal(buf, pb); err != nil {
return nil, err
}
return buf.Bytes(), nil
return protojsonMarshaler.Marshal(pb)
}
return json.Marshal(v)
}

func (j Marshaler) Unmarshal(d []byte, v interface{}) error {
if pb, ok := v.(proto.Message); ok {
return jsonpb.Unmarshal(bytes.NewReader(d), pb)
return protojson.Unmarshal(d, pb)
}
return json.Unmarshal(d, v)
}
Expand Down
18 changes: 7 additions & 11 deletions server/grpc/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,23 @@ import (
"encoding/json"
"strings"

b "bytes"

"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"go-micro.dev/v5/codec"
"go-micro.dev/v5/codec/bytes"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)

type jsonCodec struct{}
type bytesCodec struct{}
type protoCodec struct{}
type wrapCodec struct{ encoding.Codec }

var jsonpbMarshaler = &jsonpb.Marshaler{
EnumsAsInts: false,
EmitDefaults: false,
OrigName: true,
var protojsonMarshaler = protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: false,
}

var (
Expand Down Expand Up @@ -85,8 +82,7 @@ func (protoCodec) Name() string {

func (jsonCodec) Marshal(v interface{}) ([]byte, error) {
if pb, ok := v.(proto.Message); ok {
s, err := jsonpbMarshaler.MarshalToString(pb)
return []byte(s), err
return protojsonMarshaler.Marshal(pb)
}

return json.Marshal(v)
Expand All @@ -97,7 +93,7 @@ func (jsonCodec) Unmarshal(data []byte, v interface{}) error {
return nil
}
if pb, ok := v.(proto.Message); ok {
return jsonpb.Unmarshal(b.NewReader(data), pb)
return protojson.Unmarshal(data, pb)
}
return json.Unmarshal(data, v)
}
Expand Down