1
0
mirror of https://github.com/golang/go synced 2024-11-21 23:24:41 -07:00

encoding/json: return JSON field with all marshal errors

This commit is contained in:
Mike McRill 2024-11-15 19:42:34 -06:00
parent 252e9def65
commit ac6e99f737
No known key found for this signature in database
2 changed files with 74 additions and 8 deletions

View File

@ -128,15 +128,24 @@ type UnmarshalTypeError struct {
Offset int64 // error occurred after reading Offset bytes Offset int64 // error occurred after reading Offset bytes
Struct string // name of the struct type containing the field Struct string // name of the struct type containing the field
Field string // the full path from root node to the field, include embedded struct 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 { func (e *UnmarshalTypeError) Error() string {
if e.Err != nil {
return e.Err.Error()
}
if e.Struct != "" || e.Field != "" { 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 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() 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 // An UnmarshalFieldError describes a JSON object key that
// led to an unexported (and therefore unwritable) struct field. // led to an unexported (and therefore unwritable) struct field.
// //
@ -508,7 +517,15 @@ func (d *decodeState) array(v reflect.Value) error {
if u != nil { if u != nil {
start := d.readIndex() start := d.readIndex()
d.skip() 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 { if ut != nil {
d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) 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 { if u != nil {
start := d.readIndex() start := d.readIndex()
d.skip() 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 { if ut != nil {
d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) 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 isNull := item[0] == 'n' // null
u, ut, pv := indirect(v, isNull) u, ut, pv := indirect(v, isNull)
if u != nil { 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 ut != nil {
if item[0] != '"' { if item[0] != '"' {
@ -886,7 +933,13 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
} }
panic(phasePanicMsg) 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 v = pv

View File

@ -77,6 +77,18 @@ type TOuter struct {
T TAlias 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 // ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and
// without UseNumber // without UseNumber
var ifaceNumAsFloat64 = map[string]any{ 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: `"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: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"},
{CaseName: Name(""), in: "null", ptr: new(any), out: nil}, {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": [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"}}, {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), 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: `{"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: `{"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"}}, {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: 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: `{"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: 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: `{"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 // raw values with whitespace
{CaseName: Name(""), in: "\n true ", ptr: new(bool), out: true}, {CaseName: Name(""), in: "\n true ", ptr: new(bool), out: true},