Skip to content

Commit 748d386

Browse files
cspahr-tulipretailcybrcodr
authored andcommitted
jsonpb: unmarshal JSON "null" for WKT (#394)
Properly unmarshal JSON "null" field value to nil for all WKTs except for Value type. This is consistent with regular message field types.
1 parent 0a4f71a commit 748d386

File tree

2 files changed

+31
-36
lines changed

2 files changed

+31
-36
lines changed

jsonpb/jsonpb.go

+15-29
Original file line numberDiff line numberDiff line change
@@ -639,23 +639,25 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
639639

640640
// Allocate memory for pointer fields.
641641
if targetType.Kind() == reflect.Ptr {
642+
// If input value is "null" and target is a pointer type, then the field should be treated as not set
643+
// UNLESS the target is structpb.Value, in which case it should be set to structpb.NullValue.
644+
if string(inputValue) == "null" && targetType != reflect.TypeOf(&stpb.Value{}) {
645+
return nil
646+
}
642647
target.Set(reflect.New(targetType.Elem()))
648+
643649
return u.unmarshalValue(target.Elem(), inputValue, prop)
644650
}
645651

646652
if jsu, ok := target.Addr().Interface().(JSONPBUnmarshaler); ok {
647653
return jsu.UnmarshalJSONPB(u, []byte(inputValue))
648654
}
649655

650-
// Handle well-known types.
656+
// Handle well-known types that are not pointers.
651657
if w, ok := target.Addr().Interface().(wkt); ok {
652658
switch w.XXX_WellKnownType() {
653659
case "DoubleValue", "FloatValue", "Int64Value", "UInt64Value",
654660
"Int32Value", "UInt32Value", "BoolValue", "StringValue", "BytesValue":
655-
// "Wrappers use the same representation in JSON
656-
// as the wrapped primitive type, except that null is allowed."
657-
// encoding/json will turn JSON `null` into Go `nil`,
658-
// so we don't have to do any extra work.
659661
return u.unmarshalValue(target.Field(0), inputValue, prop)
660662
case "Any":
661663
// Use json.RawMessage pointer type instead of value to support pre-1.8 version.
@@ -716,55 +718,42 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
716718

717719
return nil
718720
case "Duration":
719-
ivStr := string(inputValue)
720-
if ivStr == "null" {
721-
target.Field(0).SetInt(0)
722-
target.Field(1).SetInt(0)
723-
return nil
724-
}
725-
726-
unq, err := strconv.Unquote(ivStr)
721+
unq, err := strconv.Unquote(string(inputValue))
727722
if err != nil {
728723
return err
729724
}
725+
730726
d, err := time.ParseDuration(unq)
731727
if err != nil {
732728
return fmt.Errorf("bad Duration: %v", err)
733729
}
730+
734731
ns := d.Nanoseconds()
735732
s := ns / 1e9
736733
ns %= 1e9
737734
target.Field(0).SetInt(s)
738735
target.Field(1).SetInt(ns)
739736
return nil
740737
case "Timestamp":
741-
ivStr := string(inputValue)
742-
if ivStr == "null" {
743-
target.Field(0).SetInt(0)
744-
target.Field(1).SetInt(0)
745-
return nil
746-
}
747-
748-
unq, err := strconv.Unquote(ivStr)
738+
unq, err := strconv.Unquote(string(inputValue))
749739
if err != nil {
750740
return err
751741
}
742+
752743
t, err := time.Parse(time.RFC3339Nano, unq)
753744
if err != nil {
754745
return fmt.Errorf("bad Timestamp: %v", err)
755746
}
747+
756748
target.Field(0).SetInt(t.Unix())
757749
target.Field(1).SetInt(int64(t.Nanosecond()))
758750
return nil
759751
case "Struct":
760-
if string(inputValue) == "null" {
761-
// Interpret a null struct as empty.
762-
return nil
763-
}
764752
var m map[string]json.RawMessage
765753
if err := json.Unmarshal(inputValue, &m); err != nil {
766754
return fmt.Errorf("bad StructValue: %v", err)
767755
}
756+
768757
target.Field(0).Set(reflect.ValueOf(map[string]*stpb.Value{}))
769758
for k, jv := range m {
770759
pv := &stpb.Value{}
@@ -775,14 +764,11 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
775764
}
776765
return nil
777766
case "ListValue":
778-
if string(inputValue) == "null" {
779-
// Interpret a null ListValue as empty.
780-
return nil
781-
}
782767
var s []json.RawMessage
783768
if err := json.Unmarshal(inputValue, &s); err != nil {
784769
return fmt.Errorf("bad ListValue: %v", err)
785770
}
771+
786772
target.Field(0).Set(reflect.ValueOf(make([]*stpb.Value, len(s), len(s))))
787773
for i, sv := range s {
788774
if err := u.unmarshalValue(target.Field(0).Index(i), sv, prop); err != nil {

jsonpb/jsonpb_test.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -553,12 +553,12 @@ var unmarshalingTests = []struct {
553553
{"camelName input", Unmarshaler{}, `{"oBool":true}`, &pb.Simple{OBool: proto.Bool(true)}},
554554

555555
{"Duration", Unmarshaler{}, `{"dur":"3.000s"}`, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}},
556-
{"null Duration", Unmarshaler{}, `{"dur":null}`, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 0}}},
556+
{"null Duration", Unmarshaler{}, `{"dur":null}`, &pb.KnownTypes{Dur: nil}},
557557
{"Timestamp", Unmarshaler{}, `{"ts":"2014-05-13T16:53:20.021Z"}`, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 14e8, Nanos: 21e6}}},
558558
{"PreEpochTimestamp", Unmarshaler{}, `{"ts":"1969-12-31T23:59:58.999999995Z"}`, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: -2, Nanos: 999999995}}},
559559
{"ZeroTimeTimestamp", Unmarshaler{}, `{"ts":"0001-01-01T00:00:00Z"}`, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: -62135596800, Nanos: 0}}},
560-
{"null Timestamp", Unmarshaler{}, `{"ts":null}`, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 0, Nanos: 0}}},
561-
{"null Struct", Unmarshaler{}, `{"st": null}`, &pb.KnownTypes{St: &stpb.Struct{}}},
560+
{"null Timestamp", Unmarshaler{}, `{"ts":null}`, &pb.KnownTypes{Ts: nil}},
561+
{"null Struct", Unmarshaler{}, `{"st": null}`, &pb.KnownTypes{St: nil}},
562562
{"empty Struct", Unmarshaler{}, `{"st": {}}`, &pb.KnownTypes{St: &stpb.Struct{}}},
563563
{"basic Struct", Unmarshaler{}, `{"st": {"a": "x", "b": null, "c": 3, "d": true}}`, &pb.KnownTypes{St: &stpb.Struct{Fields: map[string]*stpb.Value{
564564
"a": {Kind: &stpb.Value_StringValue{"x"}},
@@ -575,7 +575,7 @@ var unmarshalingTests = []struct {
575575
}}}},
576576
}}}},
577577
}}}},
578-
{"null ListValue", Unmarshaler{}, `{"lv": null}`, &pb.KnownTypes{Lv: &stpb.ListValue{}}},
578+
{"null ListValue", Unmarshaler{}, `{"lv": null}`, &pb.KnownTypes{Lv: nil}},
579579
{"empty ListValue", Unmarshaler{}, `{"lv": []}`, &pb.KnownTypes{Lv: &stpb.ListValue{}}},
580580
{"basic ListValue", Unmarshaler{}, `{"lv": ["x", null, 3, true]}`, &pb.KnownTypes{Lv: &stpb.ListValue{Values: []*stpb.Value{
581581
{Kind: &stpb.Value_StringValue{"x"}},
@@ -612,8 +612,17 @@ var unmarshalingTests = []struct {
612612
{"BoolValue", Unmarshaler{}, `{"bool":true}`, &pb.KnownTypes{Bool: &wpb.BoolValue{Value: true}}},
613613
{"StringValue", Unmarshaler{}, `{"str":"plush"}`, &pb.KnownTypes{Str: &wpb.StringValue{Value: "plush"}}},
614614
{"BytesValue", Unmarshaler{}, `{"bytes":"d293"}`, &pb.KnownTypes{Bytes: &wpb.BytesValue{Value: []byte("wow")}}},
615-
// `null` is also a permissible value. Let's just test one.
616-
{"null DoubleValue", Unmarshaler{}, `{"dbl":null}`, &pb.KnownTypes{Dbl: &wpb.DoubleValue{}}},
615+
616+
// Ensure that `null` as a value ends up with a nil pointer instead of a [type]Value struct.
617+
{"null DoubleValue", Unmarshaler{}, `{"dbl":null}`, &pb.KnownTypes{Dbl: nil}},
618+
{"null FloatValue", Unmarshaler{}, `{"flt":null}`, &pb.KnownTypes{Flt: nil}},
619+
{"null Int64Value", Unmarshaler{}, `{"i64":null}`, &pb.KnownTypes{I64: nil}},
620+
{"null UInt64Value", Unmarshaler{}, `{"u64":null}`, &pb.KnownTypes{U64: nil}},
621+
{"null Int32Value", Unmarshaler{}, `{"i32":null}`, &pb.KnownTypes{I32: nil}},
622+
{"null UInt32Value", Unmarshaler{}, `{"u32":null}`, &pb.KnownTypes{U32: nil}},
623+
{"null BoolValue", Unmarshaler{}, `{"bool":null}`, &pb.KnownTypes{Bool: nil}},
624+
{"null StringValue", Unmarshaler{}, `{"str":null}`, &pb.KnownTypes{Str: nil}},
625+
{"null BytesValue", Unmarshaler{}, `{"bytes":null}`, &pb.KnownTypes{Bytes: nil}},
617626
}
618627

619628
func TestUnmarshaling(t *testing.T) {
@@ -756,4 +765,4 @@ func (m *dynamicMessage) MarshalJSONPB(jm *Marshaler) ([]byte, error) {
756765
func (m *dynamicMessage) UnmarshalJSONPB(jum *Unmarshaler, js []byte) error {
757766
m.rawJson = string(js)
758767
return nil
759-
}
768+
}

0 commit comments

Comments
 (0)