aboutsummaryrefslogtreecommitdiff
path: root/src/encoding/json
diff options
context:
space:
mode:
Diffstat (limited to 'src/encoding/json')
-rw-r--r--src/encoding/json/decode.go12
-rw-r--r--src/encoding/json/decode_test.go88
-rw-r--r--src/encoding/json/stream.go5
3 files changed, 95 insertions, 10 deletions
diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go
index 44f9035358..70179e60ac 100644
--- a/src/encoding/json/decode.go
+++ b/src/encoding/json/decode.go
@@ -44,8 +44,9 @@ import (
//
// 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),
-// preferring an exact match but also accepting a case-insensitive match.
-// Unmarshal will only set exported fields of the struct.
+// preferring an exact match but also accepting a case-insensitive match. By
+// 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,
// Unmarshal stores one of these in the interface value:
@@ -275,8 +276,9 @@ type decodeState struct {
Struct string
Field string
}
- savedError error
- useNumber bool
+ savedError error
+ useNumber bool
+ disallowUnknownFields bool
}
// errPhase is used for errors that should not happen unless
@@ -713,6 +715,8 @@ func (d *decodeState) object(v reflect.Value) {
}
d.errorContext.Field = f.name
d.errorContext.Struct = v.Type().Name()
+ } else if d.disallowUnknownFields {
+ d.saveError(fmt.Errorf("json: unknown field %q", key))
}
}
diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go
index 5a72f3a7c6..9ac2b14b13 100644
--- a/src/encoding/json/decode_test.go
+++ b/src/encoding/json/decode_test.go
@@ -372,12 +372,13 @@ func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error {
}
type unmarshalTest struct {
- in string
- ptr interface{}
- out interface{}
- err error
- useNumber bool
- golden bool
+ in string
+ ptr interface{}
+ out interface{}
+ err error
+ useNumber bool
+ golden bool
+ disallowUnknownFields bool
}
type B struct {
@@ -401,6 +402,7 @@ var unmarshalTests = []unmarshalTest{
{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}`, 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: 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},
@@ -415,10 +417,13 @@ var unmarshalTests = []unmarshalTest{
// Z has a "-" tag.
{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), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true},
{in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}},
{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
{in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}},
@@ -611,9 +616,21 @@ var unmarshalTests = []unmarshalTest{
},
{
in: `{"X": 1,"Y":2}`,
+ ptr: new(S5),
+ err: fmt.Errorf("json: unknown field \"X\""),
+ disallowUnknownFields: true,
+ },
+ {
+ in: `{"X": 1,"Y":2}`,
ptr: new(S10),
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.
{
@@ -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": "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`)},
+
+ // 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) {
@@ -911,6 +984,9 @@ func TestUnmarshal(t *testing.T) {
if tt.useNumber {
dec.UseNumber()
}
+ if tt.disallowUnknownFields {
+ dec.DisallowUnknownFields()
+ }
if err := dec.Decode(v.Interface()); !reflect.DeepEqual(err, tt.err) {
t.Errorf("#%d: %v, want %v", i, err, tt.err)
continue
diff --git a/src/encoding/json/stream.go b/src/encoding/json/stream.go
index 76788f5fe7..f6b62c4cf6 100644
--- a/src/encoding/json/stream.go
+++ b/src/encoding/json/stream.go
@@ -35,6 +35,11 @@ func NewDecoder(r io.Reader) *Decoder {
// Number instead of as a float64.
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
// input and stores it in the value pointed to by v.
//