From ac6e99f73770ab3ea4a3efbc0af9d0a16f72d63d Mon Sep 17 00:00:00 2001 From: Mike McRill Date: Fri, 15 Nov 2024 19:42:34 -0600 Subject: [PATCH] encoding/json: return JSON field with all marshal errors --- src/encoding/json/decode.go | 61 +++++++++++++++++++++++++++++--- src/encoding/json/decode_test.go | 21 ++++++++--- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 98102291ab8..c0807d629a0 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -128,15 +128,24 @@ type UnmarshalTypeError struct { Offset int64 // error occurred after reading Offset bytes Struct string // name of the struct type containing the field Field string // the full path from root node to the field, include embedded struct + Err error // Error which occurred during unmarshal } func (e *UnmarshalTypeError) Error() string { + if e.Err != nil { + return e.Err.Error() + } + 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() } +func (e *UnmarshalTypeError) Unwrap() error { + return e.Err +} + // An UnmarshalFieldError describes a JSON object key that // led to an unexported (and therefore unwritable) struct field. // @@ -508,7 +517,15 @@ func (d *decodeState) array(v reflect.Value) error { if u != nil { start := d.readIndex() d.skip() - return u.UnmarshalJSON(d.data[start:d.off]) + if err := u.UnmarshalJSON(d.data[start:d.off]); err != nil { + if e, ok := err.(*UnmarshalTypeError); ok { + return e + } + + d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off), Err: err}) + } + + return nil } if ut != nil { d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) @@ -605,7 +622,16 @@ func (d *decodeState) object(v reflect.Value) error { if u != nil { start := d.readIndex() d.skip() - return u.UnmarshalJSON(d.data[start:d.off]) + err := u.UnmarshalJSON(d.data[start:d.off]) + if err != nil { + if e, ok := err.(*UnmarshalTypeError); ok { + return e + } + + d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off), Err: err}) + } + + return nil } if ut != nil { d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) @@ -861,7 +887,28 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool isNull := item[0] == 'n' // null u, ut, pv := indirect(v, isNull) if u != nil { - return u.UnmarshalJSON(item) + err := u.UnmarshalJSON(item) + if err != nil { + if e, ok := err.(*UnmarshalTypeError); ok { + return e + } + + var val string + switch item[0] { + case '"': + val = "string" + case 'n': + val = "null" + case 't', 'f': + val = "bool" + default: + val = "number" + } + + d.saveError(&UnmarshalTypeError{Value: val, Type: v.Type(), Offset: int64(d.readIndex()), Err: err}) + } + + return nil } if ut != nil { if item[0] != '"' { @@ -886,7 +933,13 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool } panic(phasePanicMsg) } - return ut.UnmarshalText(s) + + err := ut.UnmarshalText(s) + if err != nil { + d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex()), Err: err}) + } + + return nil } v = pv diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index 71895a9bb14..fb0f5b55ea2 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -77,6 +77,18 @@ type TOuter struct { T TAlias } +type EV struct { + EVS EVS +} + +type EVS string + +var EVerr = errors.New("error") + +func (*EVS) UnmarshalJSON(_ []byte) error { + return EVerr +} + // ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and // without UseNumber var ifaceNumAsFloat64 = map[string]any{ @@ -437,17 +449,18 @@ var unmarshalTests = []struct { {CaseName: Name(""), in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, {CaseName: Name(""), in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, {CaseName: Name(""), in: "null", ptr: new(any), out: nil}, - {CaseName: Name(""), in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeFor[string](), 7, "T", "X"}}, - {CaseName: Name(""), in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 8, "T", "X"}}, + {CaseName: Name(""), in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeFor[string](), 7, "T", "X", nil}}, + {CaseName: Name(""), in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 8, "T", "X", nil}}, {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, - {CaseName: Name(""), in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[SS](), 0, "W", "S"}}, - {CaseName: Name(""), in: `{"T": {"X": 23}}`, ptr: new(TOuter), out: TOuter{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 0, "TOuter", "T.X"}}, + {CaseName: Name(""), in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[SS](), 0, "W", "S", nil}}, + {CaseName: Name(""), in: `{"T": {"X": 23}}`, ptr: new(TOuter), out: TOuter{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 0, "TOuter", "T.X", nil}}, {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, {CaseName: Name(""), in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, {CaseName: Name(""), in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsNumber, useNumber: true}, + {CaseName: Name(""), in: `{"EVS": 23}`, ptr: new(EV), out: EV{}, err: &UnmarshalTypeError{"string", reflect.TypeFor[EVS](), 0, "EV", "EVS", EVerr}}, // raw values with whitespace {CaseName: Name(""), in: "\n true ", ptr: new(bool), out: true},