]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json: fix decoding of JSON null values
authorDidier Spezia <didier.06@gmail.com>
Tue, 28 Apr 2015 11:20:19 +0000 (11:20 +0000)
committerRuss Cox <rsc@golang.org>
Wed, 22 Jul 2015 15:39:54 +0000 (15:39 +0000)
JSON decoding currently fails for null values bound to any type
which does implement the JSON Unmarshaler interface without checking
for null values (such as time.Time).

It also fails for types implementing the TextUnmarshaler interface.

The expected behavior of the JSON decoding engine in such case is
to process null by keeping the value unchanged without producing
any error.

Make sure null values are handled by the decoding engine itself,
and never passed to the UnmarshalText or UnmarshalJSON methods.

Fixes #9037

Change-Id: I261d85587ba543ef6f1815555b2af9311034d5bb
Reviewed-on: https://go-review.googlesource.com/9376
Reviewed-by: Russ Cox <rsc@golang.org>
src/encoding/json/decode.go
src/encoding/json/decode_test.go

index 530e8521dc5e6eccecfd0cde8926edada7d793fe..4d17c279bd9d6821a29ec6c11b4a6f72f1c22177 100644 (file)
@@ -358,7 +358,7 @@ func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler,
                if v.IsNil() {
                        v.Set(reflect.New(v.Type().Elem()))
                }
-               if v.Type().NumMethod() > 0 {
+               if v.Type().NumMethod() > 0 && !decodingNull {
                        if u, ok := v.Interface().(Unmarshaler); ok {
                                return u, nil, reflect.Value{}
                        }
index 8aa158f08cdade6f9fe7f2d3caa91382bb646a1a..8c449d4784f20fd3eb15bce5783018a322afec4c 100644 (file)
@@ -1322,7 +1322,48 @@ func (t *Time3339) UnmarshalJSON(b []byte) error {
        return nil
 }
 
-func TestUnmarshalJSONLiteralError(t *testing.T) {
+// A Time-like type supporting the json Unmarshal interface
+type AJson struct {
+       T int
+}
+
+func (t *AJson) UnmarshalJSON(b []byte) error {
+       if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
+               return fmt.Errorf("types: failed to unmarshal non-string value %q", b)
+       }
+       if _, err := fmt.Sscanf(string(b[1:len(b)-1]), "%d", &t.T); err != nil {
+               return err
+       }
+       return nil
+}
+
+// A Time-like type supporting the text Unmarshal interface
+type AText struct {
+       T int
+}
+
+func (t *AText) UnmarshalText(b []byte) error {
+       if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
+               return fmt.Errorf("types: failed to unmarshal non-string value %q", b)
+       }
+       if _, err := fmt.Sscanf(string(b[1:len(b)-1]), "%d", &t.T); err != nil {
+               return err
+       }
+       return nil
+}
+
+// This type mixes pointers and structures, supporting json or text Unmarshal interfaces
+type STime struct {
+       X  int
+       J1 AJson
+       J2 *AJson
+       J3 **AJson
+       T1 AText
+       T2 *AText
+       T3 **AText
+}
+
+func TestUnmarshalJSONLiteralError1(t *testing.T) {
        var t3 Time3339
        err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3)
        if err == nil {
@@ -1331,6 +1372,39 @@ func TestUnmarshalJSONLiteralError(t *testing.T) {
        if !strings.Contains(err.Error(), "range") {
                t.Errorf("got err = %v; want out of range error", err)
        }
+
+       out := time.Now()
+       want := out
+       err = Unmarshal([]byte(`null`), &out)
+       if err != nil {
+               t.Fatalf("got err = %v; no error was expected", err)
+       }
+       if out != want {
+               t.Fatalf("got %q, want %q", out, want)
+       }
+}
+
+func TestUnmarshalJSONLiteralError2(t *testing.T) {
+       out := STime{
+               X:  1,
+               J1: AJson{2},
+               J2: &AJson{3},
+               J3: new(*AJson),
+               T1: AText{5},
+               T2: &AText{6},
+               T3: new(*AText),
+       }
+       want := out
+       want.J2 = nil
+       want.T2 = nil
+       // Keep the spaces as they are in the following line
+       err := Unmarshal([]byte(`{"X":1,"J1":null,"J2":null,"J3": null,"T1":null ,"T2": null , "T3":null}`), &out)
+       if err != nil {
+               t.Fatalf("got err = %v; no error was expected", err)
+       }
+       if out != want {
+               t.Fatalf("got %v, want %v", out, want)
+       }
 }
 
 // Test that extra object elements in an array do not result in a