Description
Proposal Details
Problem context
The following example just outputs panic: unhelpful error
which isn't useful in finding out which key in the JSON caused the problem, i.e "was it bar
or baz
that caused the problem?" We only have access to the value bytes (no key name) so even if we wanted to write a better error message, we couldn't.
- Playground link
- Output:
panic: unhelpful error
package main
import (
"encoding/json"
"errors"
"fmt"
)
type strawberry struct{}
func (b *strawberry) UnmarshalJSON(data []byte) error {
return errors.New("unhelpful error")
}
type myType struct {
Foo string `json:"foo,omitempty"`
Bar strawberry `json:"bar,omitempty"`
Baz strawberry `json:"baz,omitempty"`
}
func main() {
json_bytes := []byte(`{ "foo": "test", "bar": 1, "baz": 2 }`)
var data myType
if err := json.Unmarshal(json_bytes, &data); err != nil {
panic(err)
}
fmt.Println(data)
}
Compare this with the built-in types where we get some helpful output -> panic: json: cannot unmarshal number into Go struct field myType.bar of type string
because of the automatic logic behind json.UnmarshalTypeError
(see addErrorContext
).
- Playground link
- Output:
panic: json: cannot unmarshal number into Go struct field myType.bar of type string
package main
import (
"encoding/json"
"fmt"
)
type myType struct {
Foo string `json:"foo,omitempty"`
Bar string `json:"bar,omitempty"`
Baz string `json:"baz,omitempty"`
}
func main() {
json_bytes := []byte(`{ "foo": "test", "bar": 1, "baz": 2 }`)
var data myType
if err := json.Unmarshal(json_bytes, &data); err != nil {
panic(err)
}
fmt.Println(data)
}
We can partially get our desired effect by returning json.UnmarshalTypeError
ourselves but it doesn't allow for any extra context on why we failed to parse it. For example, what if the reason we failed to unmarshal was because Strawberries have at-least 200 seeds
but there is not place to add this context with the current json.UnmarshalTypeError
.
- Playground link
- Output:
panic: json: cannot unmarshal 1 into Go struct field myType.bar of type main.strawberry
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type strawberry struct{}
func (b *strawberry) UnmarshalJSON(data []byte) error {
return &json.UnmarshalTypeError{Value: string(data), Type: reflect.TypeOf((*strawberry)(nil)).Elem(), Offset: 0}
}
type myType struct {
Foo string `json:"foo,omitempty"`
Bar strawberry `json:"bar,omitempty"`
Baz strawberry `json:"baz,omitempty"`
}
func main() {
json_bytes := []byte(`{ "foo": "test", "bar": 1, "baz": 2 }`)
var data myType
if err := json.Unmarshal(json_bytes, &data); err != nil {
panic(err)
}
fmt.Println(data)
}
Proposal
Wrap any error from UnmarshalJSON
and include the field name context that generated the error.
Another possibility is to extend json.UnmarshalTypeError
to allow for additional custom error context
which would work but not everyone is going to be as meticulous to always return json.UnmarshalTypeError
to get the benefits.
I'm not writing Go everyday so there is probably a better strategy as well.