Skip to content

Commit f0a097d

Browse files
committed
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.
1 parent 78550bb commit f0a097d

File tree

8 files changed

+586
-458
lines changed

8 files changed

+586
-458
lines changed

jsonpb/jsonpb.go

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ func UnmarshalNext(dec *json.Decoder, pb proto.Message) error {
436436
if err := dec.Decode(&inputValue); err != nil {
437437
return err
438438
}
439-
return unmarshalValue(reflect.ValueOf(pb).Elem(), inputValue)
439+
return unmarshalValue(reflect.ValueOf(pb).Elem(), inputValue, nil)
440440
}
441441

442442
// Unmarshal unmarshals a JSON object stream into a protocol
@@ -455,13 +455,14 @@ func UnmarshalString(str string, pb proto.Message) error {
455455
}
456456

457457
// unmarshalValue converts/copies a value into the target.
458-
func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
458+
// prop may be nil.
459+
func unmarshalValue(target reflect.Value, inputValue json.RawMessage, prop *proto.Properties) error {
459460
targetType := target.Type()
460461

461462
// Allocate memory for pointer fields.
462463
if targetType.Kind() == reflect.Ptr {
463464
target.Set(reflect.New(targetType.Elem()))
464-
return unmarshalValue(target.Elem(), inputValue)
465+
return unmarshalValue(target.Elem(), inputValue, prop)
465466
}
466467

467468
// Handle well-known types.
@@ -476,7 +477,7 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
476477
// as the wrapped primitive type, except that null is allowed."
477478
// encoding/json will turn JSON `null` into Go `nil`,
478479
// so we don't have to do any extra work.
479-
return unmarshalValue(target.Field(0), inputValue)
480+
return unmarshalValue(target.Field(0), inputValue, prop)
480481
case "Duration":
481482
unq, err := strconv.Unquote(string(inputValue))
482483
if err != nil {
@@ -510,6 +511,27 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
510511
}
511512
}
512513

514+
// Handle enums, which have an underlying type of int32,
515+
// and may appear as strings.
516+
// The case of an enum appearing as a number is handled
517+
// at the bottom of this function.
518+
if inputValue[0] == '"' && prop != nil && prop.Enum != "" {
519+
vmap := proto.EnumValueMap(prop.Enum)
520+
// Don't need to do unquoting; valid enum names
521+
// are from a limited character set.
522+
s := inputValue[1 : len(inputValue)-1]
523+
n, ok := vmap[string(s)]
524+
if !ok {
525+
return fmt.Errorf("unknown value %q for enum %s", s, prop.Enum)
526+
}
527+
if target.Kind() == reflect.Ptr { // proto2
528+
target.Set(reflect.New(targetType.Elem()))
529+
target = target.Elem()
530+
}
531+
target.SetInt(int64(n))
532+
return nil
533+
}
534+
513535
// Handle nested messages.
514536
if targetType.Kind() == reflect.Struct {
515537
var jsonFields map[string]json.RawMessage
@@ -551,30 +573,7 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
551573
continue
552574
}
553575

554-
// Handle enums, which have an underlying type of int32,
555-
// and may appear as strings. We do this while handling
556-
// the struct so we have access to the enum info.
557-
// The case of an enum appearing as a number is handled
558-
// by the recursive call to unmarshalValue.
559-
if enum := sprops.Prop[i].Enum; valueForField[0] == '"' && enum != "" {
560-
vmap := proto.EnumValueMap(enum)
561-
// Don't need to do unquoting; valid enum names
562-
// are from a limited character set.
563-
s := valueForField[1 : len(valueForField)-1]
564-
n, ok := vmap[string(s)]
565-
if !ok {
566-
return fmt.Errorf("unknown value %q for enum %s", s, enum)
567-
}
568-
f := target.Field(i)
569-
if f.Kind() == reflect.Ptr { // proto2
570-
f.Set(reflect.New(f.Type().Elem()))
571-
f = f.Elem()
572-
}
573-
f.SetInt(int64(n))
574-
continue
575-
}
576-
577-
if err := unmarshalValue(target.Field(i), valueForField); err != nil {
576+
if err := unmarshalValue(target.Field(i), valueForField, sprops.Prop[i]); err != nil {
578577
return err
579578
}
580579
}
@@ -587,7 +586,7 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
587586
}
588587
nv := reflect.New(oop.Type.Elem())
589588
target.Field(oop.Field).Set(nv)
590-
if err := unmarshalValue(nv.Elem().Field(0), raw); err != nil {
589+
if err := unmarshalValue(nv.Elem().Field(0), raw, oop.Prop); err != nil {
591590
return err
592591
}
593592
}
@@ -613,7 +612,7 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
613612
len := len(slc)
614613
target.Set(reflect.MakeSlice(targetType, len, len))
615614
for i := 0; i < len; i++ {
616-
if err := unmarshalValue(target.Index(i), slc[i]); err != nil {
615+
if err := unmarshalValue(target.Index(i), slc[i], prop); err != nil {
617616
return err
618617
}
619618
}
@@ -627,6 +626,13 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
627626
return err
628627
}
629628
target.Set(reflect.MakeMap(targetType))
629+
var keyprop, valprop *proto.Properties
630+
if prop != nil {
631+
// These could still be nil if the protobuf metadata is broken somehow.
632+
// TODO: This won't work because the fields are unexported.
633+
// We should probably just reparse them.
634+
//keyprop, valprop = prop.mkeyprop, prop.mvalprop
635+
}
630636
for ks, raw := range mp {
631637
// Unmarshal map key. The core json library already decoded the key into a
632638
// string, so we handle that specially. Other types were quoted post-serialization.
@@ -635,14 +641,14 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
635641
k = reflect.ValueOf(ks)
636642
} else {
637643
k = reflect.New(targetType.Key()).Elem()
638-
if err := unmarshalValue(k, json.RawMessage(ks)); err != nil {
644+
if err := unmarshalValue(k, json.RawMessage(ks), keyprop); err != nil {
639645
return err
640646
}
641647
}
642648

643649
// Unmarshal map value.
644650
v := reflect.New(targetType.Elem()).Elem()
645-
if err := unmarshalValue(v, raw); err != nil {
651+
if err := unmarshalValue(v, raw, valprop); err != nil {
646652
return err
647653
}
648654
target.SetMapIndex(k, v)

jsonpb/jsonpb_test.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,18 @@ var marshalingTests = []struct {
299299
&pb.Widget{Color: pb.Widget_BLUE.Enum()}, colorPrettyJSON},
300300
{"unknown enum value object", marshalerAllOptions,
301301
&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}, colorListPrettyJSON},
302+
{"repeated proto3 enum", Marshaler{},
303+
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
304+
proto3pb.Message_PUNS,
305+
proto3pb.Message_SLAPSTICK,
306+
}},
307+
`{"rFunny":["PUNS","SLAPSTICK"]}`},
308+
{"repeated proto3 enum as int", Marshaler{EnumsAsInts: true},
309+
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
310+
proto3pb.Message_PUNS,
311+
proto3pb.Message_SLAPSTICK,
312+
}},
313+
`{"rFunny":[1,2]}`},
302314
{"empty value", marshaler, &pb.Simple3{}, `{}`},
303315
{"empty value emitted", Marshaler{EmitDefaults: true}, &pb.Simple3{}, `{"dub":0}`},
304316
{"map<int64, int32>", marshaler, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}, `{"nummy":{"1":2,"3":4}}`},
@@ -313,6 +325,9 @@ var marshalingTests = []struct {
313325
{"map<int64, string>", marshaler, &pb.Mappy{Buggy: map[int64]string{1234: "yup"}},
314326
`{"buggy":{"1234":"yup"}}`},
315327
{"map<bool, bool>", marshaler, &pb.Mappy{Booly: map[bool]bool{false: true}}, `{"booly":{"false":true}}`},
328+
// TODO: This is broken.
329+
//{"map<string, enum>", marshaler, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}, `{"enumy":{"XIV":"ROMAN"}`},
330+
{"map<string, enum as int>", Marshaler{EnumsAsInts: true}, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}, `{"enumy":{"XIV":2}}`},
316331
{"proto2 map<int64, string>", marshaler, &pb.Maps{MInt64Str: map[int64]string{213: "cat"}},
317332
`{"mInt64Str":{"213":"cat"}}`},
318333
{"proto2 map<bool, Object>", marshaler,
@@ -373,11 +388,29 @@ var unmarshalingTests = []struct {
373388
{"unknown enum value object",
374389
"{\n \"color\": 1000,\n \"r_color\": [\n \"RED\"\n ]\n}",
375390
&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}},
391+
{"repeated proto3 enum", `{"rFunny":["PUNS","SLAPSTICK"]}`,
392+
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
393+
proto3pb.Message_PUNS,
394+
proto3pb.Message_SLAPSTICK,
395+
}}},
396+
{"repeated proto3 enum as int", `{"rFunny":[1,2]}`,
397+
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
398+
proto3pb.Message_PUNS,
399+
proto3pb.Message_SLAPSTICK,
400+
}}},
401+
{"repeated proto3 enum as mix of strings and ints", `{"rFunny":["PUNS",2]}`,
402+
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
403+
proto3pb.Message_PUNS,
404+
proto3pb.Message_SLAPSTICK,
405+
}}},
376406
{"unquoted int64 object", `{"oInt64":-314}`, &pb.Simple{OInt64: proto.Int64(-314)}},
377407
{"unquoted uint64 object", `{"oUint64":123}`, &pb.Simple{OUint64: proto.Uint64(123)}},
378408
{"map<int64, int32>", `{"nummy":{"1":2,"3":4}}`, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}},
379409
{"map<string, string>", `{"strry":{"\"one\"":"two","three":"four"}}`, &pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}}},
380410
{"map<int32, Object>", `{"objjy":{"1":{"dub":1}}}`, &pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}},
411+
// TODO: This is broken.
412+
//{"map<string, enum>", `{"enumy":{"XIV":"ROMAN"}`, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}},
413+
{"map<string, enum as int>", `{"enumy":{"XIV":2}}`, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}},
381414
{"oneof", `{"salary":31000}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Salary{31000}}},
382415
{"oneof spec name", `{"country":"Australia"}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Country{"Australia"}}},
383416
{"oneof orig_name", `{"Country":"Australia"}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Country{"Australia"}}},
@@ -421,14 +454,17 @@ func TestUnmarshaling(t *testing.T) {
421454
}
422455

423456
func TestUnmarshalNext(t *testing.T) {
457+
// We only need to check against a few, not all of them.
458+
tests := unmarshalingTests[:5]
459+
424460
// Create a buffer with many concatenated JSON objects.
425461
var b bytes.Buffer
426-
for _, tt := range unmarshalingTests {
462+
for _, tt := range tests {
427463
b.WriteString(tt.json)
428464
}
429465

430466
dec := json.NewDecoder(&b)
431-
for _, tt := range unmarshalingTests {
467+
for _, tt := range tests {
432468
// Make a new instance of the type of our expected object.
433469
p := reflect.New(reflect.TypeOf(tt.pb).Elem()).Interface().(proto.Message)
434470

jsonpb/jsonpb_test_proto/more_test_objects.pb.go

Lines changed: 57 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jsonpb/jsonpb_test_proto/more_test_objects.proto

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,17 @@ message Simple3 {
3737
double dub = 1;
3838
}
3939

40+
enum Numeral {
41+
UNKNOWN = 0;
42+
ARABIC = 1;
43+
ROMAN = 2;
44+
}
45+
4046
message Mappy {
4147
map<int64, int32> nummy = 1;
4248
map<string, string> strry = 2;
4349
map<int32, Simple3> objjy = 3;
4450
map<int64, string> buggy = 4;
4551
map<bool, bool> booly = 5;
52+
map<string, Numeral> enumy = 6;
4653
}

0 commit comments

Comments
 (0)