]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json: disallow unknown fields in Decoder
authorIvan Bertona <ivan.bertona@gmail.com>
Tue, 31 Oct 2017 20:16:38 +0000 (13:16 -0700)
committerJoe Tsai <thebrokentoaster@gmail.com>
Tue, 31 Oct 2017 22:28:36 +0000 (22:28 +0000)
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/74830
Reviewed-by: Joe Tsai <joetsai@google.com>
Run-TryBot: Joe Tsai <joetsai@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/encoding/json/decode.go
src/encoding/json/decode_test.go
src/encoding/json/stream.go

index 44f903535896fae57a907cd5493e855fd6daadb0..70179e60ac3dd56357d2433c85db43cd8ae44b60 100644 (file)
@@ -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))
                        }
                }
 
index 5a72f3a7c6dbc3ec500e059b5258a3b6ae4e11ba..9ac2b14b136a9eeda7926a3c49cb67281911651e 100644 (file)
@@ -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}},
@@ -609,11 +614,23 @@ var unmarshalTests = []unmarshalTest{
                ptr: new(S5),
                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}`,
                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
index 76788f5fe7000b18859cd547b5b5b88bce729b8a..f6b62c4cf634f92aa460dfb15a50031361309558 100644 (file)
@@ -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.
 //