Commit 1a99ba55 authored by Didier Spezia's avatar Didier Spezia Committed by Russ Cox

encoding/json: fix decoding of JSON null values

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/9376Reviewed-by: 's avatarRuss Cox <rsc@golang.org>
parent 1421bc10
......@@ -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{}
}
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment