Skip to content

Commit 1909bc2

Browse files
tamirdcybrcodr
authored andcommitted
jsonpb: unmarshal JSON null to nil map/slice instead of empty instance (#404)
* jsonpb: don't allocate memory for null-value reference types This was missed in 748d386. * gofmt -s -w jsonpb/jsonpb_test.go
1 parent 748d386 commit 1909bc2

File tree

2 files changed

+59
-34
lines changed

2 files changed

+59
-34
lines changed

jsonpb/jsonpb.go

+33-29
Original file line numberDiff line numberDiff line change
@@ -919,11 +919,13 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
919919
if err := json.Unmarshal(inputValue, &slc); err != nil {
920920
return err
921921
}
922-
len := len(slc)
923-
target.Set(reflect.MakeSlice(targetType, len, len))
924-
for i := 0; i < len; i++ {
925-
if err := u.unmarshalValue(target.Index(i), slc[i], prop); err != nil {
926-
return err
922+
if slc != nil {
923+
l := len(slc)
924+
target.Set(reflect.MakeSlice(targetType, l, l))
925+
for i := 0; i < l; i++ {
926+
if err := u.unmarshalValue(target.Index(i), slc[i], prop); err != nil {
927+
return err
928+
}
927929
}
928930
}
929931
return nil
@@ -935,33 +937,35 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
935937
if err := json.Unmarshal(inputValue, &mp); err != nil {
936938
return err
937939
}
938-
target.Set(reflect.MakeMap(targetType))
939-
var keyprop, valprop *proto.Properties
940-
if prop != nil {
941-
// These could still be nil if the protobuf metadata is broken somehow.
942-
// TODO: This won't work because the fields are unexported.
943-
// We should probably just reparse them.
944-
//keyprop, valprop = prop.mkeyprop, prop.mvalprop
945-
}
946-
for ks, raw := range mp {
947-
// Unmarshal map key. The core json library already decoded the key into a
948-
// string, so we handle that specially. Other types were quoted post-serialization.
949-
var k reflect.Value
950-
if targetType.Key().Kind() == reflect.String {
951-
k = reflect.ValueOf(ks)
952-
} else {
953-
k = reflect.New(targetType.Key()).Elem()
954-
if err := u.unmarshalValue(k, json.RawMessage(ks), keyprop); err != nil {
955-
return err
956-
}
940+
if mp != nil {
941+
target.Set(reflect.MakeMap(targetType))
942+
var keyprop, valprop *proto.Properties
943+
if prop != nil {
944+
// These could still be nil if the protobuf metadata is broken somehow.
945+
// TODO: This won't work because the fields are unexported.
946+
// We should probably just reparse them.
947+
//keyprop, valprop = prop.mkeyprop, prop.mvalprop
957948
}
949+
for ks, raw := range mp {
950+
// Unmarshal map key. The core json library already decoded the key into a
951+
// string, so we handle that specially. Other types were quoted post-serialization.
952+
var k reflect.Value
953+
if targetType.Key().Kind() == reflect.String {
954+
k = reflect.ValueOf(ks)
955+
} else {
956+
k = reflect.New(targetType.Key()).Elem()
957+
if err := u.unmarshalValue(k, json.RawMessage(ks), keyprop); err != nil {
958+
return err
959+
}
960+
}
958961

959-
// Unmarshal map value.
960-
v := reflect.New(targetType.Elem()).Elem()
961-
if err := u.unmarshalValue(v, raw, valprop); err != nil {
962-
return err
962+
// Unmarshal map value.
963+
v := reflect.New(targetType.Elem()).Elem()
964+
if err := u.unmarshalValue(v, raw, valprop); err != nil {
965+
return err
966+
}
967+
target.SetMapIndex(k, v)
963968
}
964-
target.SetMapIndex(k, v)
965969
}
966970
return nil
967971
}

jsonpb/jsonpb_test.go

+26-5
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,9 @@ var marshalingTests = []struct {
379379
&pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}},
380380
`{"strry":{"\"one\"":"two","three":"four"}}`},
381381
{"map<int32, Object>", marshaler,
382-
&pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}, `{"objjy":{"1":{"dub":1}}}`},
382+
&pb.Mappy{Objjy: map[int32]*pb.Simple3{1: {Dub: 1}}}, `{"objjy":{"1":{"dub":1}}}`},
383383
{"map<int32, Object>", marshalerAllOptions,
384-
&pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}, objjyPrettyJSON},
384+
&pb.Mappy{Objjy: map[int32]*pb.Simple3{1: {Dub: 1}}}, objjyPrettyJSON},
385385
{"map<int64, string>", marshaler, &pb.Mappy{Buggy: map[int64]string{1234: "yup"}},
386386
`{"buggy":{"1234":"yup"}}`},
387387
{"map<bool, bool>", marshaler, &pb.Mappy{Booly: map[bool]bool{false: true}}, `{"booly":{"false":true}}`},
@@ -395,7 +395,7 @@ var marshalingTests = []struct {
395395
{"proto2 map<int64, string>", marshaler, &pb.Maps{MInt64Str: map[int64]string{213: "cat"}},
396396
`{"mInt64Str":{"213":"cat"}}`},
397397
{"proto2 map<bool, Object>", marshaler,
398-
&pb.Maps{MBoolSimple: map[bool]*pb.Simple{true: &pb.Simple{OInt32: proto.Int32(1)}}},
398+
&pb.Maps{MBoolSimple: map[bool]*pb.Simple{true: {OInt32: proto.Int32(1)}}},
399399
`{"mBoolSimple":{"true":{"oInt32":1}}}`},
400400
{"oneof, not set", marshaler, &pb.MsgWithOneof{}, `{}`},
401401
{"oneof, set", marshaler, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Title{"Grand Poobah"}}, `{"title":"Grand Poobah"}`},
@@ -486,7 +486,7 @@ func TestMarshalAnyJSONPBMarshaler(t *testing.T) {
486486
}
487487
// after custom marshaling, it's round-tripped through JSON decoding/encoding already,
488488
// so the keys are sorted, whitespace is compacted, and "@type" key has been added
489-
expected := `{"@type":"type.googleapis.com/` + dynamicMessageName +`","baz":[0,1,2,3],"foo":"bar"}`
489+
expected := `{"@type":"type.googleapis.com/` + dynamicMessageName + `","baz":[0,1,2,3],"foo":"bar"}`
490490
if str != expected {
491491
t.Errorf("marshalling JSON produced incorrect output: got %s, wanted %s", str, expected)
492492
}
@@ -535,7 +535,7 @@ var unmarshalingTests = []struct {
535535
{"-Inf", Unmarshaler{}, `{"oDouble":"-Infinity"}`, &pb.Simple{ODouble: proto.Float64(math.Inf(-1))}},
536536
{"map<int64, int32>", Unmarshaler{}, `{"nummy":{"1":2,"3":4}}`, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}},
537537
{"map<string, string>", Unmarshaler{}, `{"strry":{"\"one\"":"two","three":"four"}}`, &pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}}},
538-
{"map<int32, Object>", Unmarshaler{}, `{"objjy":{"1":{"dub":1}}}`, &pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}},
538+
{"map<int32, Object>", Unmarshaler{}, `{"objjy":{"1":{"dub":1}}}`, &pb.Mappy{Objjy: map[int32]*pb.Simple3{1: {Dub: 1}}}},
539539
{"proto2 extension", Unmarshaler{}, realNumberJSON, realNumber},
540540
{"Any with message", Unmarshaler{}, anySimpleJSON, anySimple},
541541
{"Any with message and indent", Unmarshaler{}, anySimplePrettyJSON, anySimple},
@@ -645,6 +645,26 @@ func TestUnmarshaling(t *testing.T) {
645645
}
646646
}
647647

648+
func TestUnmarshalNullArray(t *testing.T) {
649+
var repeats pb.Repeats
650+
if err := UnmarshalString(`{"rBool":null}`, &repeats); err != nil {
651+
t.Fatal(err)
652+
}
653+
if !reflect.DeepEqual(repeats, pb.Repeats{}) {
654+
t.Errorf("got non-nil fields in [%#v]", repeats)
655+
}
656+
}
657+
658+
func TestUnmarshalNullObject(t *testing.T) {
659+
var maps pb.Maps
660+
if err := UnmarshalString(`{"mInt64Str":null}`, &maps); err != nil {
661+
t.Fatal(err)
662+
}
663+
if !reflect.DeepEqual(maps, pb.Maps{}) {
664+
t.Errorf("got non-nil fields in [%#v]", maps)
665+
}
666+
}
667+
648668
func TestUnmarshalNext(t *testing.T) {
649669
// We only need to check against a few, not all of them.
650670
tests := unmarshalingTests[:5]
@@ -736,6 +756,7 @@ func TestUnmarshalAnyJSONPBUnmarshaler(t *testing.T) {
736756
const (
737757
dynamicMessageName = "google.protobuf.jsonpb.testing.dynamicMessage"
738758
)
759+
739760
func init() {
740761
// we register the custom type below so that we can use it in Any types
741762
proto.RegisterType((*dynamicMessage)(nil), dynamicMessageName)

0 commit comments

Comments
 (0)