Skip to content

Commit ee5af46

Browse files
committed
encoding/json: avoid misleading errors under goexperiment.jsonv2
The jsontext package represents the location of JSON errors using a JSON Pointer (RFC 6901). This uses the JSON type system. Unfortunately the v1 json.UnmarshalTypeError assumes a Go struct-based mechanism for reporting the location of errors (and has historically never been implemented correctly since it was a weird mix of both JSON and Go namespaces; see #43126). Trying to map a JSON Pointer into UnmarshalTypeError.{Struct,Field} is difficult to get right without teaching jsontext about the Go type system. To reduce the probability of misleading errors, check whether the last token looks like a JSON array index and if so, elide the phrase "into Go struct field". Fixes #74801 Change-Id: Id2088ffb9c339a9238ed38c90223d86a89422842 Reviewed-on: https://go-review.googlesource.com/c/go/+/710676 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Damien Neil <dneil@google.com>
1 parent 11d3d2f commit ee5af46

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

src/encoding/json/v2_decode.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"fmt"
1515
"reflect"
1616
"strconv"
17+
"strings"
1718

1819
"encoding/json/internal/jsonwire"
1920
"encoding/json/jsontext"
@@ -119,7 +120,20 @@ type UnmarshalTypeError struct {
119120
func (e *UnmarshalTypeError) Error() string {
120121
var s string
121122
if e.Struct != "" || e.Field != "" {
122-
s = "json: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String()
123+
// The design of UnmarshalTypeError overly assumes a struct-based
124+
// Go representation for the JSON value.
125+
// The logic in jsontext represents paths using a JSON Pointer,
126+
// which is agnostic to the Go type system.
127+
// Trying to convert a JSON Pointer into a UnmarshalTypeError.Field
128+
// is difficult. As a heuristic, if the last path token looks like
129+
// an index into a JSON array (e.g., ".foo.bar.0"),
130+
// avoid the phrase "Go struct field ".
131+
intoWhat := "Go struct field "
132+
i := strings.LastIndexByte(e.Field, '.') + len(".")
133+
if len(e.Field[i:]) > 0 && strings.TrimRight(e.Field[i:], "0123456789") == "" {
134+
intoWhat = "" // likely a Go slice or array
135+
}
136+
s = "json: cannot unmarshal " + e.Value + " into " + intoWhat + e.Struct + "." + e.Field + " of type " + e.Type.String()
123137
} else {
124138
s = "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
125139
}

src/encoding/json/v2_decode_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,6 +2363,34 @@ func TestUnmarshalTypeError(t *testing.T) {
23632363
}
23642364
}
23652365

2366+
func TestUnmarshalTypeErrorMessage(t *testing.T) {
2367+
err := &UnmarshalTypeError{
2368+
Value: "number 5",
2369+
Type: reflect.TypeFor[int](),
2370+
Offset: 1234,
2371+
Struct: "Root",
2372+
}
2373+
2374+
for _, tt := range []struct {
2375+
field string
2376+
want string
2377+
}{
2378+
{"", "json: cannot unmarshal number 5 into Go struct field Root. of type int"},
2379+
{"1", "json: cannot unmarshal number 5 into Root.1 of type int"},
2380+
{"foo", "json: cannot unmarshal number 5 into Go struct field Root.foo of type int"},
2381+
{"foo.1", "json: cannot unmarshal number 5 into Root.foo.1 of type int"},
2382+
{"foo.bar", "json: cannot unmarshal number 5 into Go struct field Root.foo.bar of type int"},
2383+
{"foo.bar.1", "json: cannot unmarshal number 5 into Root.foo.bar.1 of type int"},
2384+
{"foo.bar.baz", "json: cannot unmarshal number 5 into Go struct field Root.foo.bar.baz of type int"},
2385+
} {
2386+
err.Field = tt.field
2387+
got := err.Error()
2388+
if got != tt.want {
2389+
t.Errorf("Error:\n\tgot: %v\n\twant: %v", got, tt.want)
2390+
}
2391+
}
2392+
}
2393+
23662394
func TestUnmarshalSyntax(t *testing.T) {
23672395
var x any
23682396
tests := []struct {

0 commit comments

Comments
 (0)