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