jsonpb: Fix handling of repeated enums.
Enums used in maps is still broken, but commented-out test data
is now there, together with TODOs.
Fixes #164.
diff --git a/jsonpb/jsonpb.go b/jsonpb/jsonpb.go
index c4094ac..69ed7fd 100644
--- a/jsonpb/jsonpb.go
+++ b/jsonpb/jsonpb.go
@@ -436,7 +436,7 @@
if err := dec.Decode(&inputValue); err != nil {
return err
}
- return unmarshalValue(reflect.ValueOf(pb).Elem(), inputValue)
+ return unmarshalValue(reflect.ValueOf(pb).Elem(), inputValue, nil)
}
// Unmarshal unmarshals a JSON object stream into a protocol
@@ -455,13 +455,14 @@
}
// unmarshalValue converts/copies a value into the target.
-func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
+// prop may be nil.
+func unmarshalValue(target reflect.Value, inputValue json.RawMessage, prop *proto.Properties) error {
targetType := target.Type()
// Allocate memory for pointer fields.
if targetType.Kind() == reflect.Ptr {
target.Set(reflect.New(targetType.Elem()))
- return unmarshalValue(target.Elem(), inputValue)
+ return unmarshalValue(target.Elem(), inputValue, prop)
}
// Handle well-known types.
@@ -476,7 +477,7 @@
// as the wrapped primitive type, except that null is allowed."
// encoding/json will turn JSON `null` into Go `nil`,
// so we don't have to do any extra work.
- return unmarshalValue(target.Field(0), inputValue)
+ return unmarshalValue(target.Field(0), inputValue, prop)
case "Duration":
unq, err := strconv.Unquote(string(inputValue))
if err != nil {
@@ -510,6 +511,27 @@
}
}
+ // Handle enums, which have an underlying type of int32,
+ // and may appear as strings.
+ // The case of an enum appearing as a number is handled
+ // at the bottom of this function.
+ if inputValue[0] == '"' && prop != nil && prop.Enum != "" {
+ vmap := proto.EnumValueMap(prop.Enum)
+ // Don't need to do unquoting; valid enum names
+ // are from a limited character set.
+ s := inputValue[1 : len(inputValue)-1]
+ n, ok := vmap[string(s)]
+ if !ok {
+ return fmt.Errorf("unknown value %q for enum %s", s, prop.Enum)
+ }
+ if target.Kind() == reflect.Ptr { // proto2
+ target.Set(reflect.New(targetType.Elem()))
+ target = target.Elem()
+ }
+ target.SetInt(int64(n))
+ return nil
+ }
+
// Handle nested messages.
if targetType.Kind() == reflect.Struct {
var jsonFields map[string]json.RawMessage
@@ -551,30 +573,7 @@
continue
}
- // Handle enums, which have an underlying type of int32,
- // and may appear as strings. We do this while handling
- // the struct so we have access to the enum info.
- // The case of an enum appearing as a number is handled
- // by the recursive call to unmarshalValue.
- if enum := sprops.Prop[i].Enum; valueForField[0] == '"' && enum != "" {
- vmap := proto.EnumValueMap(enum)
- // Don't need to do unquoting; valid enum names
- // are from a limited character set.
- s := valueForField[1 : len(valueForField)-1]
- n, ok := vmap[string(s)]
- if !ok {
- return fmt.Errorf("unknown value %q for enum %s", s, enum)
- }
- f := target.Field(i)
- if f.Kind() == reflect.Ptr { // proto2
- f.Set(reflect.New(f.Type().Elem()))
- f = f.Elem()
- }
- f.SetInt(int64(n))
- continue
- }
-
- if err := unmarshalValue(target.Field(i), valueForField); err != nil {
+ if err := unmarshalValue(target.Field(i), valueForField, sprops.Prop[i]); err != nil {
return err
}
}
@@ -587,7 +586,7 @@
}
nv := reflect.New(oop.Type.Elem())
target.Field(oop.Field).Set(nv)
- if err := unmarshalValue(nv.Elem().Field(0), raw); err != nil {
+ if err := unmarshalValue(nv.Elem().Field(0), raw, oop.Prop); err != nil {
return err
}
}
@@ -613,7 +612,7 @@
len := len(slc)
target.Set(reflect.MakeSlice(targetType, len, len))
for i := 0; i < len; i++ {
- if err := unmarshalValue(target.Index(i), slc[i]); err != nil {
+ if err := unmarshalValue(target.Index(i), slc[i], prop); err != nil {
return err
}
}
@@ -627,6 +626,13 @@
return err
}
target.Set(reflect.MakeMap(targetType))
+ var keyprop, valprop *proto.Properties
+ if prop != nil {
+ // These could still be nil if the protobuf metadata is broken somehow.
+ // TODO: This won't work because the fields are unexported.
+ // We should probably just reparse them.
+ //keyprop, valprop = prop.mkeyprop, prop.mvalprop
+ }
for ks, raw := range mp {
// Unmarshal map key. The core json library already decoded the key into a
// string, so we handle that specially. Other types were quoted post-serialization.
@@ -635,14 +641,14 @@
k = reflect.ValueOf(ks)
} else {
k = reflect.New(targetType.Key()).Elem()
- if err := unmarshalValue(k, json.RawMessage(ks)); err != nil {
+ if err := unmarshalValue(k, json.RawMessage(ks), keyprop); err != nil {
return err
}
}
// Unmarshal map value.
v := reflect.New(targetType.Elem()).Elem()
- if err := unmarshalValue(v, raw); err != nil {
+ if err := unmarshalValue(v, raw, valprop); err != nil {
return err
}
target.SetMapIndex(k, v)