Просмотр исходного кода

Fix jsonpb parsing of enums as names for proto3.

Fixes #59.
David Symonds 10 лет назад
Родитель
Сommit
5d7f79bcf3
3 измененных файлов с 46 добавлено и 15 удалено
  1. 32 11
      jsonpb/jsonpb.go
  2. 8 4
      jsonpb/jsonpb_test.go
  3. 6 0
      proto/properties.go

+ 32 - 11
jsonpb/jsonpb.go

@@ -297,7 +297,7 @@ func Unmarshal(r io.Reader, pb proto.Message) error {
 // on a JSON string. This function is lenient and will decode any options
 // on a JSON string. This function is lenient and will decode any options
 // permutations of the related Marshaler.
 // permutations of the related Marshaler.
 func UnmarshalString(str string, pb proto.Message) error {
 func UnmarshalString(str string, pb proto.Message) error {
-	return Unmarshal(bytes.NewReader([]byte(str)), pb)
+	return Unmarshal(strings.NewReader(str), pb)
 }
 }
 
 
 // unmarshalValue converts/copies a value into the target.
 // unmarshalValue converts/copies a value into the target.
@@ -317,6 +317,7 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
 			return err
 			return err
 		}
 		}
 
 
+		sprops := proto.GetProperties(targetType)
 		for i := 0; i < target.NumField(); i++ {
 		for i := 0; i < target.NumField(); i++ {
 			ft := target.Type().Field(i)
 			ft := target.Type().Field(i)
 			if strings.HasPrefix(ft.Name, "XXX_") {
 			if strings.HasPrefix(ft.Name, "XXX_") {
@@ -324,15 +325,40 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
 			}
 			}
 			fieldName := jsonFieldName(ft)
 			fieldName := jsonFieldName(ft)
 
 
-			if valueForField, ok := jsonFields[fieldName]; ok {
-				if err := unmarshalValue(target.Field(i), valueForField); err != nil {
-					return err
+			valueForField, ok := jsonFields[fieldName]
+			if !ok {
+				continue
+			}
+			delete(jsonFields, fieldName)
+
+			// 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()
 				}
 				}
-				delete(jsonFields, fieldName)
+				f.SetInt(int64(n))
+				continue
+			}
+
+			if err := unmarshalValue(target.Field(i), valueForField); err != nil {
+				return err
 			}
 			}
 		}
 		}
 		// Check for any oneof fields.
 		// Check for any oneof fields.
-		sprops := proto.GetProperties(targetType)
 		for fname, raw := range jsonFields {
 		for fname, raw := range jsonFields {
 			if oop, ok := sprops.OneofTypes[fname]; ok {
 			if oop, ok := sprops.OneofTypes[fname]; ok {
 				nv := reflect.New(oop.Type.Elem())
 				nv := reflect.New(oop.Type.Elem())
@@ -412,11 +438,6 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
 	return json.Unmarshal(inputValue, target.Addr().Interface())
 	return json.Unmarshal(inputValue, target.Addr().Interface())
 }
 }
 
 
-// hasUnmarshalJSON is a interface implemented by protocol buffer enums.
-type hasUnmarshalJSON interface {
-	UnmarshalJSON(data []byte) error
-}
-
 // jsonFieldName returns the field name to use.
 // jsonFieldName returns the field name to use.
 func jsonFieldName(f reflect.StructField) string {
 func jsonFieldName(f reflect.StructField) string {
 	var prop proto.Properties
 	var prop proto.Properties

+ 8 - 4
jsonpb/jsonpb_test.go

@@ -37,6 +37,7 @@ import (
 
 
 	pb "github.com/golang/protobuf/jsonpb/jsonpb_test_proto"
 	pb "github.com/golang/protobuf/jsonpb/jsonpb_test_proto"
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/proto"
+	proto3pb "github.com/golang/protobuf/proto/proto3_proto"
 )
 )
 
 
 var (
 var (
@@ -320,6 +321,8 @@ var unmarshalingTests = []struct {
 	{"nested message/enum pretty object", complexObjectPrettyJSON, complexObject},
 	{"nested message/enum pretty object", complexObjectPrettyJSON, complexObject},
 	{"enum-string object", `{"color":"BLUE"}`, &pb.Widget{Color: pb.Widget_BLUE.Enum()}},
 	{"enum-string object", `{"color":"BLUE"}`, &pb.Widget{Color: pb.Widget_BLUE.Enum()}},
 	{"enum-value object", "{\n \"color\": 2\n}", &pb.Widget{Color: pb.Widget_BLUE.Enum()}},
 	{"enum-value object", "{\n \"color\": 2\n}", &pb.Widget{Color: pb.Widget_BLUE.Enum()}},
+	{"proto3 enum string", `{"hilarity":"PUNS"}`, &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
+	{"proto3 enum value", `{"hilarity":1}`, &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
 	{"unknown enum value object",
 	{"unknown enum value object",
 		"{\n  \"color\": 1000,\n  \"r_color\": [\n    \"RED\"\n  ]\n}",
 		"{\n  \"color\": 1000,\n  \"r_color\": [\n    \"RED\"\n  ]\n}",
 		&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}},
 		&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}},
@@ -354,15 +357,16 @@ func TestUnmarshaling(t *testing.T) {
 var unmarshalingShouldError = []struct {
 var unmarshalingShouldError = []struct {
 	desc string
 	desc string
 	in   string
 	in   string
+	pb   proto.Message
 }{
 }{
-	{"a value", "666"},
-	{"gibberish", "{adskja123;l23=-="},
+	{"a value", "666", new(pb.Simple)},
+	{"gibberish", "{adskja123;l23=-=", new(pb.Simple)},
+	{"unknown enum name", `{"hilarity":"DAVE"}`, new(proto3pb.Message)},
 }
 }
 
 
 func TestUnmarshalingBadInput(t *testing.T) {
 func TestUnmarshalingBadInput(t *testing.T) {
 	for _, tt := range unmarshalingShouldError {
 	for _, tt := range unmarshalingShouldError {
-		obj := &pb.Simple{}
-		err := UnmarshalString(tt.in, obj)
+		err := UnmarshalString(tt.in, tt.pb)
 		if err == nil {
 		if err == nil {
 			t.Errorf("an error was expected when parsing %q instead of an object", tt.desc)
 			t.Errorf("an error was expected when parsing %q instead of an object", tt.desc)
 		}
 		}

+ 6 - 0
proto/properties.go

@@ -803,3 +803,9 @@ func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[
 	}
 	}
 	enumValueMaps[typeName] = valueMap
 	enumValueMaps[typeName] = valueMap
 }
 }
+
+// EnumValueMap returns the mapping from names to integers of the
+// enum type enumType, or a nil if not found.
+func EnumValueMap(enumType string) map[string]int32 {
+	return enumValueMaps[enumType]
+}