-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
encoding/json: include field name in unmarshal error messages when extracting time.Time #22816
Comments
One thing I'd like to see here is consistency in the errors returned from json.Unmarshal and it's analog's. For example, always returning a json.UnmarshalTypeError that perhaps also holds some ref to the underlying error as well as the existing the Value, Type, Offset, Struct and Field information related to the error condition. Doing this, rather than just bubbling up the underlying error, (in this case a time.ParseError), enables some more consistency to handling unmarshaling errors. I had a need for this on a project recently and decided do exactly this, so I copied the encoding/json package and made the following changes in decoder.go: Plus sign in column 0 denotes additions, Minus sign in column 0 denotes subtractions. // An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
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
+ Err error // error, if any which occurred during unmarshal
} // literalStore decodes a literal stored in item into v.
//
// fromQuoted indicates whether this literal came from unwrapping a
// string from the ",string" struct tag option. this is used only to
// produce more helpful error messages.
func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) {
// Check for unmarshaler.
if len(item) == 0 {
//Empty string given
d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
return
}
isNull := item[0] == 'n' // null
u, ut, pv := d.indirect(v, isNull)
if u != nil {
err := u.UnmarshalJSON(item)
if err != nil {
- d.error(err)
+ var val string
+ switch item[0] {
+ case '"':
+ val = "string"
+ case 'n':
+ val = "null"
+ case 't', 'f':
+ val = "bool"
+ default:
+ val = "number"
+ }
+ d.error(&UnmarshalTypeError{Value: val, Type: v.Type(), Offset: int64(d.off), Err: err})
}
return
}
... I only had to make a couple small changes in the tests to get them all to pass, though, to be fair, I'm not sure that I have all the possible code paths are covered. |
Here's a few lines added to the array function that adds Field context for arrays. Plus sign in column 0 denotes additions // array consumes an array from d.data[d.off-1:], decoding into the value v.
// the first byte of the array ('[') has been read already.
func (d *decodeState) array(v reflect.Value) {
+ arrayField := d.errorContext.Field
// Check for unmarshaler.
u, ut, pv := d.indirect(v, false)
if u != nil {
d.off--
err := u.UnmarshalJSON(d.next())
if err != nil {
d.error(err)
}
return
}
if ut != nil {
d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)})
d.off--
d.next()
return
}
v = pv
// Check type of target.
switch v.Kind() {
case reflect.Interface:
if v.NumMethod() == 0 {
// Decoding into nil interface? Switch to non-reflect code.
v.Set(reflect.ValueOf(d.arrayInterface()))
return
}
// Otherwise it's invalid.
fallthrough
default:
d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)})
d.off--
d.next()
return
case reflect.Array:
case reflect.Slice:
break
}
i := 0
for {
+ d.errorContext.Field = fmt.Sprintf("%s[%v]", arrayField, i)
// Look ahead for ] - can only happen on first iteration.
op := d.scanWhile(scanSkipSpace)
if op == scanEndArray {
break
}
// Back up so d.value can have the byte we just read.
d.off--
d.scan.undo(op)
// Get element of array, growing if necessary.
if v.Kind() == reflect.Slice {
// Grow slice if necessary
if i >= v.Cap() {
newcap := v.Cap() + v.Cap()/2
if newcap < 4 {
newcap = 4
}
newv := reflect.MakeSlice(v.Type(), v.Len(), newcap)
reflect.Copy(newv, v)
v.Set(newv)
}
if i >= v.Len() {
v.SetLen(i + 1)
}
}
if i < v.Len() {
// Decode into element.
d.value(v.Index(i))
} else {
// Ran out of fixed array: skip.
d.value(reflect.Value{})
}
i++
// Next token must be , or ].
op = d.scanWhile(scanSkipSpace)
if op == scanEndArray {
break
}
if op != scanArrayValue {
d.error(errPhase)
}
}
if i < v.Len() {
if v.Kind() == reflect.Array {
// Array. Zero the rest.
z := reflect.Zero(v.Type().Elem())
for ; i < v.Len(); i++ {
v.Index(i).Set(z)
}
} else {
v.SetLen(i)
}
}
if i == 0 && v.Kind() == reflect.Slice {
v.Set(reflect.MakeSlice(v.Type(), 0, 0))
}
+ d.errorContext.Field = arrayField
} |
Have you tried to submit PR for this change? |
i can submit a pr for this issue |
Something like this would be really helpful for informing users where there is a JSON syntax error. Currently, there's no way for an error bubbled up from an Unmarshal interface to include what field it is coming from, and it leads to extremely opaque JSON errors when unmarshaling. |
Change https://go.dev/cl/628556 mentions this issue: |
allocated from #6716 .
What version of Go are you using (
go version
)?go1.9.2 linux/amd64
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (
go env
)?GOARCH="amd64"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
What did you do?
run https://play.golang.org/p/YnlDi-3DMP
If possible, provide a recipe for reproducing the error.
A complete runnable program is good.
A link on play.golang.org is best.
What did you expect to see?
Field name with error message
What did you see instead?
Error without field name
The text was updated successfully, but these errors were encountered: