Commit 2596a0c0 authored by Ivan Bertona's avatar Ivan Bertona Committed by Joe Tsai

encoding/json: disallow unknown fields in Decoder

Add a DisallowUnknownFields flag to Decoder.

DisallowUnknownFields causes the Decoder to return an error when
the the decoding destination is a struct and the input contains
object keys which do not match any non-ignored, public field the
destination, including keys whose value is set to null.

Note: this fix has already been worked on in 27231, which seems
to be abandoned. This version is a slightly simpler implementation
and is up to date with the master branch.

Fixes #15314

Change-Id: I987a5857c52018df334f4d1a2360649c44a7175d
Reviewed-on: https://go-review.googlesource.com/74830Reviewed-by: default avatarJoe Tsai <joetsai@google.com>
Run-TryBot: Joe Tsai <joetsai@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent d58f4e9b
...@@ -44,8 +44,9 @@ import ( ...@@ -44,8 +44,9 @@ import (
// //
// To unmarshal JSON into a struct, Unmarshal matches incoming object // To unmarshal JSON into a struct, Unmarshal matches incoming object
// keys to the keys used by Marshal (either the struct field name or its tag), // keys to the keys used by Marshal (either the struct field name or its tag),
// preferring an exact match but also accepting a case-insensitive match. // preferring an exact match but also accepting a case-insensitive match. By
// Unmarshal will only set exported fields of the struct. // default, object keys which don't have a corresponding struct field are
// ignored (see Decoder.DisallowUnknownFields for an alternative).
// //
// To unmarshal JSON into an interface value, // To unmarshal JSON into an interface value,
// Unmarshal stores one of these in the interface value: // Unmarshal stores one of these in the interface value:
...@@ -275,8 +276,9 @@ type decodeState struct { ...@@ -275,8 +276,9 @@ type decodeState struct {
Struct string Struct string
Field string Field string
} }
savedError error savedError error
useNumber bool useNumber bool
disallowUnknownFields bool
} }
// errPhase is used for errors that should not happen unless // errPhase is used for errors that should not happen unless
...@@ -713,6 +715,8 @@ func (d *decodeState) object(v reflect.Value) { ...@@ -713,6 +715,8 @@ func (d *decodeState) object(v reflect.Value) {
} }
d.errorContext.Field = f.name d.errorContext.Field = f.name
d.errorContext.Struct = v.Type().Name() d.errorContext.Struct = v.Type().Name()
} else if d.disallowUnknownFields {
d.saveError(fmt.Errorf("json: unknown field %q", key))
} }
} }
......
...@@ -372,12 +372,13 @@ func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { ...@@ -372,12 +372,13 @@ func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error {
} }
type unmarshalTest struct { type unmarshalTest struct {
in string in string
ptr interface{} ptr interface{}
out interface{} out interface{}
err error err error
useNumber bool useNumber bool
golden bool golden bool
disallowUnknownFields bool
} }
type B struct { type B struct {
...@@ -401,6 +402,7 @@ var unmarshalTests = []unmarshalTest{ ...@@ -401,6 +402,7 @@ var unmarshalTests = []unmarshalTest{
{in: "null", ptr: new(interface{}), out: nil}, {in: "null", ptr: new(interface{}), out: nil},
{in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}}, {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}},
{in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}},
{in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true},
{in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}},
{in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true},
{in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64}, {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64},
...@@ -415,10 +417,13 @@ var unmarshalTests = []unmarshalTest{ ...@@ -415,10 +417,13 @@ var unmarshalTests = []unmarshalTest{
// Z has a "-" tag. // Z has a "-" tag.
{in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, {in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}},
{in: `{"Y": 1, "Z": 2}`, ptr: new(T), err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true},
{in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}},
{in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true},
{in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, {in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}},
{in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, {in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}},
{in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true},
// syntax errors // syntax errors
{in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}}, {in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}},
...@@ -609,11 +614,23 @@ var unmarshalTests = []unmarshalTest{ ...@@ -609,11 +614,23 @@ var unmarshalTests = []unmarshalTest{
ptr: new(S5), ptr: new(S5),
out: S5{S8: S8{S9: S9{Y: 2}}}, out: S5{S8: S8{S9: S9{Y: 2}}},
}, },
{
in: `{"X": 1,"Y":2}`,
ptr: new(S5),
err: fmt.Errorf("json: unknown field \"X\""),
disallowUnknownFields: true,
},
{ {
in: `{"X": 1,"Y":2}`, in: `{"X": 1,"Y":2}`,
ptr: new(S10), ptr: new(S10),
out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}},
}, },
{
in: `{"X": 1,"Y":2}`,
ptr: new(S10),
err: fmt.Errorf("json: unknown field \"X\""),
disallowUnknownFields: true,
},
// invalid UTF-8 is coerced to valid UTF-8. // invalid UTF-8 is coerced to valid UTF-8.
{ {
...@@ -793,6 +810,62 @@ var unmarshalTests = []unmarshalTest{ ...@@ -793,6 +810,62 @@ var unmarshalTests = []unmarshalTest{
{in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, {in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)},
{in: `{"B": "null"}`, ptr: new(B), out: B{false}}, {in: `{"B": "null"}`, ptr: new(B), out: B{false}},
{in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)}, {in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)},
// additional tests for disallowUnknownFields
{
in: `{
"Level0": 1,
"Level1b": 2,
"Level1c": 3,
"x": 4,
"Level1a": 5,
"LEVEL1B": 6,
"e": {
"Level1a": 8,
"Level1b": 9,
"Level1c": 10,
"Level1d": 11,
"x": 12
},
"Loop1": 13,
"Loop2": 14,
"X": 15,
"Y": 16,
"Z": 17,
"Q": 18,
"extra": true
}`,
ptr: new(Top),
err: fmt.Errorf("json: unknown field \"extra\""),
disallowUnknownFields: true,
},
{
in: `{
"Level0": 1,
"Level1b": 2,
"Level1c": 3,
"x": 4,
"Level1a": 5,
"LEVEL1B": 6,
"e": {
"Level1a": 8,
"Level1b": 9,
"Level1c": 10,
"Level1d": 11,
"x": 12,
"extra": null
},
"Loop1": 13,
"Loop2": 14,
"X": 15,
"Y": 16,
"Z": 17,
"Q": 18
}`,
ptr: new(Top),
err: fmt.Errorf("json: unknown field \"extra\""),
disallowUnknownFields: true,
},
} }
func TestMarshal(t *testing.T) { func TestMarshal(t *testing.T) {
...@@ -911,6 +984,9 @@ func TestUnmarshal(t *testing.T) { ...@@ -911,6 +984,9 @@ func TestUnmarshal(t *testing.T) {
if tt.useNumber { if tt.useNumber {
dec.UseNumber() dec.UseNumber()
} }
if tt.disallowUnknownFields {
dec.DisallowUnknownFields()
}
if err := dec.Decode(v.Interface()); !reflect.DeepEqual(err, tt.err) { if err := dec.Decode(v.Interface()); !reflect.DeepEqual(err, tt.err) {
t.Errorf("#%d: %v, want %v", i, err, tt.err) t.Errorf("#%d: %v, want %v", i, err, tt.err)
continue continue
......
...@@ -35,6 +35,11 @@ func NewDecoder(r io.Reader) *Decoder { ...@@ -35,6 +35,11 @@ func NewDecoder(r io.Reader) *Decoder {
// Number instead of as a float64. // Number instead of as a float64.
func (dec *Decoder) UseNumber() { dec.d.useNumber = true } func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
// DisallowUnknownFields causes the Decoder to return an error when the destination
// is a struct and the input contains object keys which do not match any
// non-ignored, exported fields in the destination.
func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true }
// Decode reads the next JSON-encoded value from its // Decode reads the next JSON-encoded value from its
// input and stores it in the value pointed to by v. // input and stores it in the value pointed to by v.
// //
......
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