Implement oneof support.
This includes the code generation changes,
and the infrastructure to wire it up to the encode/decode machinery.
The overall API changes are these:
- oneofs in a message are replaced by a single interface field
- each field in a oneof gets a distinguished type that satisfies
the corresponding interface
- a type switch may be used to distinguish between oneof fields
Fixes #29.
diff --git a/jsonpb/jsonpb.go b/jsonpb/jsonpb.go
index fb5e087..2663c92 100644
--- a/jsonpb/jsonpb.go
+++ b/jsonpb/jsonpb.go
@@ -111,6 +111,18 @@
}
}
+ // Oneof fields need special handling.
+ if valueField.Tag.Get("protobuf_oneof") != "" {
+ // value is an interface containing &T{real_value}.
+ sv := value.Elem().Elem() // interface -> *T -> T
+ value = sv.Field(0)
+ valueField = sv.Type().Field(0)
+
+ var p proto.Properties
+ p.Parse(sv.Type().Field(0).Tag.Get("protobuf"))
+ fieldName = p.OrigName
+ }
+
out.write(writeBeforeField)
if m.Indent != "" {
out.write(indent)
@@ -319,6 +331,44 @@
delete(jsonFields, fieldName)
}
}
+ // Check for any oneof fields.
+ // This might be slow; we can optimise it if it becomes a problem.
+ type oneofMessage interface {
+ XXX_OneofFuncs() (func(proto.Message, *proto.Buffer) error, func(proto.Message, int, int, *proto.Buffer) (bool, error), []interface{})
+ }
+ var oneofTypes []interface{}
+ if om, ok := reflect.Zero(reflect.PtrTo(targetType)).Interface().(oneofMessage); ok {
+ _, _, oneofTypes = om.XXX_OneofFuncs()
+ }
+ for fname, raw := range jsonFields {
+ for _, oot := range oneofTypes {
+ sp := reflect.ValueOf(oot).Type() // *T
+ var props proto.Properties
+ props.Parse(sp.Elem().Field(0).Tag.Get("protobuf"))
+ if props.OrigName != fname {
+ continue
+ }
+ nv := reflect.New(sp.Elem())
+ // There will be exactly one interface field that
+ // this new value is assignable to.
+ for i := 0; i < targetType.NumField(); i++ {
+ f := targetType.Field(i)
+ if f.Type.Kind() != reflect.Interface {
+ continue
+ }
+ if !nv.Type().AssignableTo(f.Type) {
+ continue
+ }
+ target.Field(i).Set(nv)
+ break
+ }
+ if err := unmarshalValue(nv.Elem().Field(0), raw); err != nil {
+ return err
+ }
+ delete(jsonFields, fname)
+ break
+ }
+ }
if len(jsonFields) > 0 {
// Pick any field to be the scapegoat.
var f string
diff --git a/jsonpb/jsonpb_test.go b/jsonpb/jsonpb_test.go
index 180d5d2..200e02c 100644
--- a/jsonpb/jsonpb_test.go
+++ b/jsonpb/jsonpb_test.go
@@ -32,6 +32,7 @@
package jsonpb
import (
+ "reflect"
"testing"
pb "github.com/golang/protobuf/jsonpb/jsonpb_test_proto"
@@ -291,6 +292,8 @@
{"proto2 map<bool, Object>", marshaler,
&pb.Maps{MBoolSimple: map[bool]*pb.Simple{true: &pb.Simple{OInt32: proto.Int32(1)}}},
`{"m_bool_simple":{"true":{"o_int32":1}}}`},
+ {"oneof, not set", marshaler, &pb.MsgWithOneof{}, `{}`},
+ {"oneof, set", marshaler, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Title{"Grand Poobah"}}, `{"title":"Grand Poobah"}`},
}
func TestMarshaling(t *testing.T) {
@@ -325,13 +328,13 @@
{"map<int64, int32>", `{"nummy":{"1":2,"3":4}}`, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}},
{"map<string, string>", `{"strry":{"\"one\"":"two","three":"four"}}`, &pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}}},
{"map<int32, Object>", `{"objjy":{"1":{"dub":1}}}`, &pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}},
+ {"oneof", `{"salary":31000}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Salary{31000}}},
}
func TestUnmarshaling(t *testing.T) {
for _, tt := range unmarshalingTests {
// Make a new instance of the type of our expected object.
- p := proto.Clone(tt.pb)
- p.Reset()
+ p := reflect.New(reflect.TypeOf(tt.pb).Elem()).Interface().(proto.Message)
err := UnmarshalString(tt.json, p)
if err != nil {
diff --git a/jsonpb/jsonpb_test_proto/more_test_objects.pb.go b/jsonpb/jsonpb_test_proto/more_test_objects.pb.go
index 615d57a..2634853 100644
--- a/jsonpb/jsonpb_test_proto/more_test_objects.pb.go
+++ b/jsonpb/jsonpb_test_proto/more_test_objects.pb.go
@@ -16,9 +16,13 @@
package jsonpb
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
type Simple3 struct {
Dub float64 `protobuf:"fixed64,1,opt,name=dub" json:"dub,omitempty"`
@@ -74,6 +78,3 @@
}
return nil
}
-
-func init() {
-}
diff --git a/jsonpb/jsonpb_test_proto/test_objects.pb.go b/jsonpb/jsonpb_test_proto/test_objects.pb.go
index 1f2f061..8c5b025 100644
--- a/jsonpb/jsonpb_test_proto/test_objects.pb.go
+++ b/jsonpb/jsonpb_test_proto/test_objects.pb.go
@@ -5,10 +5,12 @@
package jsonpb
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type Widget_Color int32
@@ -322,6 +324,100 @@
return nil
}
+type MsgWithOneof struct {
+ // Types that are valid to be assigned to Union:
+ // *MsgWithOneof_Title
+ // *MsgWithOneof_Salary
+ Union isMsgWithOneof_Union `protobuf_oneof:"union"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *MsgWithOneof) Reset() { *m = MsgWithOneof{} }
+func (m *MsgWithOneof) String() string { return proto.CompactTextString(m) }
+func (*MsgWithOneof) ProtoMessage() {}
+
+type isMsgWithOneof_Union interface {
+ isMsgWithOneof_Union()
+}
+
+type MsgWithOneof_Title struct {
+ Title string `protobuf:"bytes,1,opt,name=title"`
+}
+type MsgWithOneof_Salary struct {
+ Salary int64 `protobuf:"varint,2,opt,name=salary"`
+}
+
+func (*MsgWithOneof_Title) isMsgWithOneof_Union() {}
+func (*MsgWithOneof_Salary) isMsgWithOneof_Union() {}
+
+func (m *MsgWithOneof) GetUnion() isMsgWithOneof_Union {
+ if m != nil {
+ return m.Union
+ }
+ return nil
+}
+
+func (m *MsgWithOneof) GetTitle() string {
+ if x, ok := m.GetUnion().(*MsgWithOneof_Title); ok {
+ return x.Title
+ }
+ return ""
+}
+
+func (m *MsgWithOneof) GetSalary() int64 {
+ if x, ok := m.GetUnion().(*MsgWithOneof_Salary); ok {
+ return x.Salary
+ }
+ return 0
+}
+
+// XXX_OneofFuncs is for the internal use of the proto package.
+func (*MsgWithOneof) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), []interface{}) {
+ return _MsgWithOneof_OneofMarshaler, _MsgWithOneof_OneofUnmarshaler, []interface{}{
+ (*MsgWithOneof_Title)(nil),
+ (*MsgWithOneof_Salary)(nil),
+ }
+}
+
+func _MsgWithOneof_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
+ m := msg.(*MsgWithOneof)
+ // union
+ switch x := m.Union.(type) {
+ case *MsgWithOneof_Title:
+ b.EncodeVarint(1<<3 | proto.WireBytes)
+ b.EncodeStringBytes(x.Title)
+ case *MsgWithOneof_Salary:
+ b.EncodeVarint(2<<3 | proto.WireVarint)
+ b.EncodeVarint(uint64(x.Salary))
+ case nil:
+ default:
+ return fmt.Errorf("MsgWithOneof.Union has unexpected type %T", x)
+ }
+ return nil
+}
+
+func _MsgWithOneof_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
+ m := msg.(*MsgWithOneof)
+ switch tag {
+ case 1: // union.title
+ if wire != proto.WireBytes {
+ return true, proto.ErrInternalBadWireType
+ }
+ x, err := b.DecodeStringBytes()
+ m.Union = &MsgWithOneof_Title{x}
+ return true, err
+ case 2: // union.salary
+ if wire != proto.WireVarint {
+ return true, proto.ErrInternalBadWireType
+ }
+ x, err := b.DecodeVarint()
+ m.Union = &MsgWithOneof_Salary{int64(x)}
+ return true, err
+ default:
+ return false, nil
+ }
+}
+
func init() {
proto.RegisterEnum("jsonpb.Widget_Color", Widget_Color_name, Widget_Color_value)
}
diff --git a/jsonpb/jsonpb_test_proto/test_objects.proto b/jsonpb/jsonpb_test_proto/test_objects.proto
index e48a3e8..85700bf 100644
--- a/jsonpb/jsonpb_test_proto/test_objects.proto
+++ b/jsonpb/jsonpb_test_proto/test_objects.proto
@@ -84,3 +84,10 @@
map<int64, string> m_int64_str = 1;
map<bool, Simple> m_bool_simple = 2;
}
+
+message MsgWithOneof {
+ oneof union {
+ string title = 1;
+ int64 salary = 2;
+ }
+}