Skip to content

Commit f444b48

Browse files
committed
encoding/json: fix decoding of null into Unmarshaler, TextUnmarshaler
1. Define behavior for Unmarshal of JSON null into Unmarshaler and TextUnmarshaler. Specifically, an Unmarshaler will be given the literal null and can decide what to do (because otherwise json.RawMessage is impossible to implement), and a TextUnmarshaler will be skipped over (because there is no text to unmarshal), like most other inappropriate types. Document this in Unmarshal, with a reminder in UnmarshalJSON about handling null. 2. Test all this. 3. Fix the TextUnmarshaler case, which was returning an unmarshalling error, to match the definition. 4. Fix the error that had been used for the TextUnmarshaler, since it was claiming that there was a JSON string when in fact the problem was NOT having a string. 5. Adjust time.Time and big.Int's UnmarshalJSON to ignore null, as is conventional. Fixes #9037. Change-Id: If78350414eb8dda712867dc8f4ca35a9db041b0c Reviewed-on: https://go-review.googlesource.com/30944 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent c6185aa commit f444b48

File tree

5 files changed

+231
-37
lines changed

5 files changed

+231
-37
lines changed

src/cmd/compile/internal/big/intmarsh.go

+4
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,9 @@ func (x *Int) MarshalJSON() ([]byte, error) {
7070

7171
// UnmarshalJSON implements the json.Unmarshaler interface.
7272
func (z *Int) UnmarshalJSON(text []byte) error {
73+
// Ignore null, like in the main JSON package.
74+
if string(text) == "null" {
75+
return nil
76+
}
7377
return z.UnmarshalText(text)
7478
}

src/encoding/json/decode.go

+26-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ import (
3434
// the value pointed at by the pointer. If the pointer is nil, Unmarshal
3535
// allocates a new value for it to point to.
3636
//
37+
// To unmarshal JSON into a value implementing the Unmarshaler interface,
38+
// Unmarshal calls that value's UnmarshalJSON method, including
39+
// when the input is a JSON null.
40+
// Otherwise, if the value implements encoding.TextUnmarshaler
41+
// and the input is a JSON quoted string, Unmarshal calls that value's
42+
// UnmarshalText method with the unquoted form of the string.
43+
//
3744
// To unmarshal JSON into a struct, Unmarshal matches incoming object
3845
// keys to the keys used by Marshal (either the struct field name or its tag),
3946
// preferring an exact match but also accepting a case-insensitive match.
@@ -102,6 +109,9 @@ func Unmarshal(data []byte, v interface{}) error {
102109
// The input can be assumed to be a valid encoding of
103110
// a JSON value. UnmarshalJSON must copy the JSON data
104111
// if it wishes to retain the data after returning.
112+
//
113+
// By convention, to approximate the behavior of Unmarshal itself,
114+
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
105115
type Unmarshaler interface {
106116
UnmarshalJSON([]byte) error
107117
}
@@ -458,8 +468,10 @@ func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler,
458468
if u, ok := v.Interface().(Unmarshaler); ok {
459469
return u, nil, reflect.Value{}
460470
}
461-
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
462-
return nil, u, reflect.Value{}
471+
if !decodingNull {
472+
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
473+
return nil, u, reflect.Value{}
474+
}
463475
}
464476
}
465477
v = v.Elem()
@@ -814,8 +826,8 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
814826
d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
815827
return
816828
}
817-
wantptr := item[0] == 'n' // null
818-
u, ut, pv := d.indirect(v, wantptr)
829+
isNull := item[0] == 'n' // null
830+
u, ut, pv := d.indirect(v, isNull)
819831
if u != nil {
820832
err := u.UnmarshalJSON(item)
821833
if err != nil {
@@ -828,7 +840,16 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
828840
if fromQuoted {
829841
d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
830842
} else {
831-
d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.off)})
843+
var val string
844+
switch item[0] {
845+
case 'n':
846+
val = "null"
847+
case 't', 'f':
848+
val = "bool"
849+
default:
850+
val = "number"
851+
}
852+
d.saveError(&UnmarshalTypeError{Value: val, Type: v.Type(), Offset: int64(d.off)})
832853
}
833854
return
834855
}

src/encoding/json/decode_test.go

+193-32
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"image"
1313
"math"
14+
"math/big"
1415
"net"
1516
"reflect"
1617
"strconv"
@@ -1524,40 +1525,148 @@ func TestInterfaceSet(t *testing.T) {
15241525
}
15251526
}
15261527

1528+
type NullTest struct {
1529+
Bool bool
1530+
Int int
1531+
Int8 int8
1532+
Int16 int16
1533+
Int32 int32
1534+
Int64 int64
1535+
Uint uint
1536+
Uint8 uint8
1537+
Uint16 uint16
1538+
Uint32 uint32
1539+
Uint64 uint64
1540+
Float32 float32
1541+
Float64 float64
1542+
String string
1543+
PBool *bool
1544+
Map map[string]string
1545+
Slice []string
1546+
Interface interface{}
1547+
1548+
PRaw *RawMessage
1549+
PTime *time.Time
1550+
PBigInt *big.Int
1551+
PText *MustNotUnmarshalText
1552+
PBuffer *bytes.Buffer // has methods, just not relevant ones
1553+
PStruct *struct{}
1554+
1555+
Raw RawMessage
1556+
Time time.Time
1557+
BigInt big.Int
1558+
Text MustNotUnmarshalText
1559+
Buffer bytes.Buffer
1560+
Struct struct{}
1561+
}
1562+
1563+
type NullTestStrings struct {
1564+
Bool bool `json:",string"`
1565+
Int int `json:",string"`
1566+
Int8 int8 `json:",string"`
1567+
Int16 int16 `json:",string"`
1568+
Int32 int32 `json:",string"`
1569+
Int64 int64 `json:",string"`
1570+
Uint uint `json:",string"`
1571+
Uint8 uint8 `json:",string"`
1572+
Uint16 uint16 `json:",string"`
1573+
Uint32 uint32 `json:",string"`
1574+
Uint64 uint64 `json:",string"`
1575+
Float32 float32 `json:",string"`
1576+
Float64 float64 `json:",string"`
1577+
String string `json:",string"`
1578+
PBool *bool `json:",string"`
1579+
Map map[string]string `json:",string"`
1580+
Slice []string `json:",string"`
1581+
Interface interface{} `json:",string"`
1582+
1583+
PRaw *RawMessage `json:",string"`
1584+
PTime *time.Time `json:",string"`
1585+
PBigInt *big.Int `json:",string"`
1586+
PText *MustNotUnmarshalText `json:",string"`
1587+
PBuffer *bytes.Buffer `json:",string"`
1588+
PStruct *struct{} `json:",string"`
1589+
1590+
Raw RawMessage `json:",string"`
1591+
Time time.Time `json:",string"`
1592+
BigInt big.Int `json:",string"`
1593+
Text MustNotUnmarshalText `json:",string"`
1594+
Buffer bytes.Buffer `json:",string"`
1595+
Struct struct{} `json:",string"`
1596+
}
1597+
15271598
// JSON null values should be ignored for primitives and string values instead of resulting in an error.
15281599
// Issue 2540
15291600
func TestUnmarshalNulls(t *testing.T) {
1601+
// Unmarshal docs:
1602+
// The JSON null value unmarshals into an interface, map, pointer, or slice
1603+
// by setting that Go value to nil. Because null is often used in JSON to mean
1604+
// ``not present,'' unmarshaling a JSON null into any other Go type has no effect
1605+
// on the value and produces no error.
1606+
15301607
jsonData := []byte(`{
1531-
"Bool" : null,
1532-
"Int" : null,
1533-
"Int8" : null,
1534-
"Int16" : null,
1535-
"Int32" : null,
1536-
"Int64" : null,
1537-
"Uint" : null,
1538-
"Uint8" : null,
1539-
"Uint16" : null,
1540-
"Uint32" : null,
1541-
"Uint64" : null,
1542-
"Float32" : null,
1543-
"Float64" : null,
1544-
"String" : null}`)
1545-
1546-
nulls := All{
1547-
Bool: true,
1548-
Int: 2,
1549-
Int8: 3,
1550-
Int16: 4,
1551-
Int32: 5,
1552-
Int64: 6,
1553-
Uint: 7,
1554-
Uint8: 8,
1555-
Uint16: 9,
1556-
Uint32: 10,
1557-
Uint64: 11,
1558-
Float32: 12.1,
1559-
Float64: 13.1,
1560-
String: "14"}
1608+
"Bool" : null,
1609+
"Int" : null,
1610+
"Int8" : null,
1611+
"Int16" : null,
1612+
"Int32" : null,
1613+
"Int64" : null,
1614+
"Uint" : null,
1615+
"Uint8" : null,
1616+
"Uint16" : null,
1617+
"Uint32" : null,
1618+
"Uint64" : null,
1619+
"Float32" : null,
1620+
"Float64" : null,
1621+
"String" : null,
1622+
"PBool": null,
1623+
"Map": null,
1624+
"Slice": null,
1625+
"Interface": null,
1626+
"PRaw": null,
1627+
"PTime": null,
1628+
"PBigInt": null,
1629+
"PText": null,
1630+
"PBuffer": null,
1631+
"PStruct": null,
1632+
"Raw": null,
1633+
"Time": null,
1634+
"BigInt": null,
1635+
"Text": null,
1636+
"Buffer": null,
1637+
"Struct": null
1638+
}`)
1639+
nulls := NullTest{
1640+
Bool: true,
1641+
Int: 2,
1642+
Int8: 3,
1643+
Int16: 4,
1644+
Int32: 5,
1645+
Int64: 6,
1646+
Uint: 7,
1647+
Uint8: 8,
1648+
Uint16: 9,
1649+
Uint32: 10,
1650+
Uint64: 11,
1651+
Float32: 12.1,
1652+
Float64: 13.1,
1653+
String: "14",
1654+
PBool: new(bool),
1655+
Map: map[string]string{},
1656+
Slice: []string{},
1657+
Interface: new(MustNotUnmarshalJSON),
1658+
PRaw: new(RawMessage),
1659+
PTime: new(time.Time),
1660+
PBigInt: new(big.Int),
1661+
PText: new(MustNotUnmarshalText),
1662+
PStruct: new(struct{}),
1663+
PBuffer: new(bytes.Buffer),
1664+
Raw: RawMessage("123"),
1665+
Time: time.Unix(123456789, 0),
1666+
BigInt: *big.NewInt(123),
1667+
}
1668+
1669+
before := nulls.Time.String()
15611670

15621671
err := Unmarshal(jsonData, &nulls)
15631672
if err != nil {
@@ -1566,9 +1675,61 @@ func TestUnmarshalNulls(t *testing.T) {
15661675
if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 ||
15671676
nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 ||
15681677
nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" {
1569-
15701678
t.Errorf("Unmarshal of null values affected primitives")
15711679
}
1680+
1681+
if nulls.PBool != nil {
1682+
t.Errorf("Unmarshal of null did not clear nulls.PBool")
1683+
}
1684+
if nulls.Map != nil {
1685+
t.Errorf("Unmarshal of null did not clear nulls.Map")
1686+
}
1687+
if nulls.Slice != nil {
1688+
t.Errorf("Unmarshal of null did not clear nulls.Slice")
1689+
}
1690+
if nulls.Interface != nil {
1691+
t.Errorf("Unmarshal of null did not clear nulls.Interface")
1692+
}
1693+
if nulls.PRaw != nil {
1694+
t.Errorf("Unmarshal of null did not clear nulls.PRaw")
1695+
}
1696+
if nulls.PTime != nil {
1697+
t.Errorf("Unmarshal of null did not clear nulls.PTime")
1698+
}
1699+
if nulls.PBigInt != nil {
1700+
t.Errorf("Unmarshal of null did not clear nulls.PBigInt")
1701+
}
1702+
if nulls.PText != nil {
1703+
t.Errorf("Unmarshal of null did not clear nulls.PText")
1704+
}
1705+
if nulls.PBuffer != nil {
1706+
t.Errorf("Unmarshal of null did not clear nulls.PBuffer")
1707+
}
1708+
if nulls.PStruct != nil {
1709+
t.Errorf("Unmarshal of null did not clear nulls.PStruct")
1710+
}
1711+
1712+
if string(nulls.Raw) != "null" {
1713+
t.Errorf("Unmarshal of RawMessage null did not record null: %v", string(nulls.Raw))
1714+
}
1715+
if nulls.Time.String() != before {
1716+
t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String())
1717+
}
1718+
if nulls.BigInt.String() != "123" {
1719+
t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String())
1720+
}
1721+
}
1722+
1723+
type MustNotUnmarshalJSON struct{}
1724+
1725+
func (x MustNotUnmarshalJSON) UnmarshalJSON(data []byte) error {
1726+
return errors.New("MustNotUnmarshalJSON was used")
1727+
}
1728+
1729+
type MustNotUnmarshalText struct{}
1730+
1731+
func (x MustNotUnmarshalText) UnmarshalText(text []byte) error {
1732+
return errors.New("MustNotUnmarshalText was used")
15721733
}
15731734

15741735
func TestStringKind(t *testing.T) {
@@ -1807,7 +1968,7 @@ var invalidUnmarshalTextTests = []struct {
18071968
{nil, "json: Unmarshal(nil)"},
18081969
{struct{}{}, "json: Unmarshal(non-pointer struct {})"},
18091970
{(*int)(nil), "json: Unmarshal(nil *int)"},
1810-
{new(net.IP), "json: cannot unmarshal string into Go value of type *net.IP"},
1971+
{new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"},
18111972
}
18121973

18131974
func TestInvalidUnmarshalText(t *testing.T) {

src/math/big/intmarsh.go

+4
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,9 @@ func (x *Int) MarshalJSON() ([]byte, error) {
7070

7171
// UnmarshalJSON implements the json.Unmarshaler interface.
7272
func (z *Int) UnmarshalJSON(text []byte) error {
73+
// Ignore null, like in the main JSON package.
74+
if string(text) == "null" {
75+
return nil
76+
}
7377
return z.UnmarshalText(text)
7478
}

src/time/time.go

+4
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,10 @@ func (t Time) MarshalJSON() ([]byte, error) {
950950
// UnmarshalJSON implements the json.Unmarshaler interface.
951951
// The time is expected to be a quoted string in RFC 3339 format.
952952
func (t *Time) UnmarshalJSON(data []byte) error {
953+
// Ignore null, like in the main JSON package.
954+
if string(data) == "null" {
955+
return nil
956+
}
953957
// Fractional seconds are handled implicitly by Parse.
954958
var err error
955959
*t, err = Parse(`"`+RFC3339+`"`, string(data))

0 commit comments

Comments
 (0)