]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json: add struct and field name to UnmarshalTypeError message
authorJirka Daněk <dnk@mail.muni.cz>
Mon, 18 Jan 2016 15:26:05 +0000 (16:26 +0100)
committerRuss Cox <rsc@golang.org>
Wed, 5 Oct 2016 20:28:59 +0000 (20:28 +0000)
The UnmarshalTypeError has two new fields Struct and Field,
used when constructing the error message.

Fixes #6716.

Change-Id: I67da171480a9491960b3ae81893770644180f848
Reviewed-on: https://go-review.googlesource.com/18692
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

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

index 2eda875bfdee8ab10080f096357891a8c49f2dfb..ceaecec67cf6e5d15e385853a23f5b01d1fb8256 100644 (file)
@@ -112,9 +112,14 @@ type UnmarshalTypeError struct {
        Value  string       // description of JSON value - "bool", "array", "number -5"
        Type   reflect.Type // type of Go value it could not be assigned to
        Offset int64        // error occurred after reading Offset bytes
+       Struct string       // name of the struct type containing the field
+       Field  string       // name of the field holding the Go value
 }
 
 func (e *UnmarshalTypeError) Error() string {
+       if e.Struct != "" || e.Field != "" {
+               return "json: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String()
+       }
        return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
 }
 
@@ -248,10 +253,14 @@ func isValidNumber(s string) bool {
 
 // decodeState represents the state while decoding a JSON value.
 type decodeState struct {
-       data       []byte
-       off        int // read offset in data
-       scan       scanner
-       nextscan   scanner // for calls to nextValue
+       data         []byte
+       off          int // read offset in data
+       scan         scanner
+       nextscan     scanner  // for calls to nextValue
+       errorContext struct { // provides context for type errors
+               Struct string
+               Field  string
+       }
        savedError error
        useNumber  bool
 }
@@ -265,22 +274,37 @@ func (d *decodeState) init(data []byte) *decodeState {
        d.data = data
        d.off = 0
        d.savedError = nil
+       d.errorContext.Struct = ""
+       d.errorContext.Field = ""
        return d
 }
 
 // error aborts the decoding by panicking with err.
 func (d *decodeState) error(err error) {
-       panic(err)
+       panic(d.addErrorContext(err))
 }
 
 // saveError saves the first err it is called with,
 // for reporting at the end of the unmarshal.
 func (d *decodeState) saveError(err error) {
        if d.savedError == nil {
-               d.savedError = err
+               d.savedError = d.addErrorContext(err)
        }
 }
 
+// addErrorContext returns a new error enhanced with information from d.errorContext
+func (d *decodeState) addErrorContext(err error) error {
+       if d.errorContext.Struct != "" || d.errorContext.Field != "" {
+               switch err := err.(type) {
+               case *UnmarshalTypeError:
+                       err.Struct = d.errorContext.Struct
+                       err.Field = d.errorContext.Field
+                       return err
+               }
+       }
+       return err
+}
+
 // next cuts off and returns the next full JSON value in d.data[d.off:].
 // The next value is known to be an object or array, not a literal.
 func (d *decodeState) next() []byte {
@@ -457,7 +481,7 @@ func (d *decodeState) array(v reflect.Value) {
                return
        }
        if ut != nil {
-               d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)})
+               d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)})
                d.off--
                d.next()
                return
@@ -476,7 +500,7 @@ func (d *decodeState) array(v reflect.Value) {
                // Otherwise it's invalid.
                fallthrough
        default:
-               d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)})
+               d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)})
                d.off--
                d.next()
                return
@@ -566,7 +590,7 @@ func (d *decodeState) object(v reflect.Value) {
                return
        }
        if ut != nil {
-               d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
+               d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)})
                d.off--
                d.next() // skip over { } in input
                return
@@ -594,7 +618,7 @@ func (d *decodeState) object(v reflect.Value) {
                        reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
                default:
                        if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) {
-                               d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)})
                                d.off--
                                d.next() // skip over { } in input
                                return
@@ -604,9 +628,9 @@ func (d *decodeState) object(v reflect.Value) {
                        v.Set(reflect.MakeMap(t))
                }
        case reflect.Struct:
-
+               // ok
        default:
-               d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
+               d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)})
                d.off--
                d.next() // skip over { } in input
                return
@@ -671,6 +695,8 @@ func (d *decodeState) object(v reflect.Value) {
                                        }
                                        subv = subv.Field(i)
                                }
+                               d.errorContext.Field = f.name
+                               d.errorContext.Struct = v.Type().Name()
                        }
                }
 
@@ -682,7 +708,6 @@ func (d *decodeState) object(v reflect.Value) {
                        d.error(errPhase)
                }
 
-               // Read value.
                if destring {
                        switch qv := d.valueQuoted().(type) {
                        case nil:
@@ -714,7 +739,7 @@ func (d *decodeState) object(v reflect.Value) {
                                        s := string(key)
                                        n, err := strconv.ParseInt(s, 10, 64)
                                        if err != nil || reflect.Zero(kt).OverflowInt(n) {
-                                               d.saveError(&UnmarshalTypeError{"number " + s, kt, int64(start + 1)})
+                                               d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)})
                                                return
                                        }
                                        kv = reflect.ValueOf(n).Convert(kt)
@@ -722,7 +747,7 @@ func (d *decodeState) object(v reflect.Value) {
                                        s := string(key)
                                        n, err := strconv.ParseUint(s, 10, 64)
                                        if err != nil || reflect.Zero(kt).OverflowUint(n) {
-                                               d.saveError(&UnmarshalTypeError{"number " + s, kt, int64(start + 1)})
+                                               d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)})
                                                return
                                        }
                                        kv = reflect.ValueOf(n).Convert(kt)
@@ -741,6 +766,9 @@ func (d *decodeState) object(v reflect.Value) {
                if op != scanObjectValue {
                        d.error(errPhase)
                }
+
+               d.errorContext.Struct = ""
+               d.errorContext.Field = ""
        }
 }
 
@@ -767,7 +795,7 @@ func (d *decodeState) convertNumber(s string) (interface{}, error) {
        }
        f, err := strconv.ParseFloat(s, 64)
        if err != nil {
-               return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)}
+               return nil, &UnmarshalTypeError{Value: "number " + s, Type: reflect.TypeOf(0.0), Offset: int64(d.off)}
        }
        return f, nil
 }
@@ -800,7 +828,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                        if fromQuoted {
                                d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
                        } else {
-                               d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.off)})
                        }
                        return
                }
@@ -835,7 +863,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                        if fromQuoted {
                                d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
                        } else {
-                               d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.off)})
                        }
                case reflect.Bool:
                        v.SetBool(value)
@@ -843,7 +871,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                        if v.NumMethod() == 0 {
                                v.Set(reflect.ValueOf(value))
                        } else {
-                               d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.off)})
                        }
                }
 
@@ -858,10 +886,10 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                }
                switch v.Kind() {
                default:
-                       d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
+                       d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.off)})
                case reflect.Slice:
                        if v.Type().Elem().Kind() != reflect.Uint8 {
-                               d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.off)})
                                break
                        }
                        b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
@@ -877,7 +905,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                        if v.NumMethod() == 0 {
                                v.Set(reflect.ValueOf(string(s)))
                        } else {
-                               d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.off)})
                        }
                }
 
@@ -902,7 +930,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                        if fromQuoted {
                                d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
                        } else {
-                               d.error(&UnmarshalTypeError{"number", v.Type(), int64(d.off)})
+                               d.error(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.off)})
                        }
                case reflect.Interface:
                        n, err := d.convertNumber(s)
@@ -911,7 +939,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                                break
                        }
                        if v.NumMethod() != 0 {
-                               d.saveError(&UnmarshalTypeError{"number", v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.off)})
                                break
                        }
                        v.Set(reflect.ValueOf(n))
@@ -919,7 +947,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
                        n, err := strconv.ParseInt(s, 10, 64)
                        if err != nil || v.OverflowInt(n) {
-                               d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.off)})
                                break
                        }
                        v.SetInt(n)
@@ -927,7 +955,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
                        n, err := strconv.ParseUint(s, 10, 64)
                        if err != nil || v.OverflowUint(n) {
-                               d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.off)})
                                break
                        }
                        v.SetUint(n)
@@ -935,7 +963,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
                case reflect.Float32, reflect.Float64:
                        n, err := strconv.ParseFloat(s, v.Type().Bits())
                        if err != nil || v.OverflowFloat(n) {
-                               d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.off)})
                                break
                        }
                        v.SetFloat(n)
index 04fbd7524dee2f04eec770aacb1ad521bb3561d8..37dbfeb5f3dc5e8e67a99503a7b7c9e3a95c513b 100644 (file)
@@ -33,6 +33,11 @@ type V struct {
        F1 interface{}
        F2 int32
        F3 Number
+       F4 *VOuter
+}
+
+type VOuter struct {
+       V V
 }
 
 // ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and
@@ -389,7 +394,7 @@ var unmarshalTests = []unmarshalTest{
        {in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"},
        {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"},
        {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}},
+       {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: `{"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},
@@ -504,22 +509,22 @@ var unmarshalTests = []unmarshalTest{
        {
                in:  `{"abc":"abc"}`,
                ptr: new(map[int]string),
-               err: &UnmarshalTypeError{"number abc", reflect.TypeOf(0), 2},
+               err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeOf(0), Offset: 2},
        },
        {
                in:  `{"256":"abc"}`,
                ptr: new(map[uint8]string),
-               err: &UnmarshalTypeError{"number 256", reflect.TypeOf(uint8(0)), 2},
+               err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeOf(uint8(0)), Offset: 2},
        },
        {
                in:  `{"128":"abc"}`,
                ptr: new(map[int8]string),
-               err: &UnmarshalTypeError{"number 128", reflect.TypeOf(int8(0)), 2},
+               err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeOf(int8(0)), Offset: 2},
        },
        {
                in:  `{"-1":"abc"}`,
                ptr: new(map[uint8]string),
-               err: &UnmarshalTypeError{"number -1", reflect.TypeOf(uint8(0)), 2},
+               err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeOf(uint8(0)), Offset: 2},
        },
 
        // Map keys can be encoding.TextUnmarshalers.
@@ -653,12 +658,12 @@ var unmarshalTests = []unmarshalTest{
        {
                in:  `{"2009-11-10T23:00:00Z": "hello world"}`,
                ptr: &map[Point]string{},
-               err: &UnmarshalTypeError{"object", reflect.TypeOf(map[Point]string{}), 1},
+               err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1},
        },
        {
                in:  `{"asdf": "hello world"}`,
                ptr: &map[unmarshaler]string{},
-               err: &UnmarshalTypeError{"object", reflect.TypeOf(map[unmarshaler]string{}), 1},
+               err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1},
        },
 
        // related to issue 13783.
@@ -750,6 +755,29 @@ var unmarshalTests = []unmarshalTest{
        {in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true},
        {in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true},
        {in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false},
+
+       {
+               in:  `{"V": {"F2": "hello"}}`,
+               ptr: new(VOuter),
+               err: &UnmarshalTypeError{
+                       Value:  "string",
+                       Struct: "V",
+                       Field:  "F2",
+                       Type:   reflect.TypeOf(int32(0)),
+                       Offset: 20,
+               },
+       },
+       {
+               in:  `{"V": {"F4": {}, "F2": "hello"}}`,
+               ptr: new(VOuter),
+               err: &UnmarshalTypeError{
+                       Value:  "string",
+                       Struct: "V",
+                       Field:  "F2",
+                       Type:   reflect.TypeOf(int32(0)),
+                       Offset: 30,
+               },
+       },
 }
 
 func TestMarshal(t *testing.T) {