From: Joe Tsai Date: Sat, 11 Oct 2025 18:57:46 +0000 (-0700) Subject: encoding/json: avoid misleading errors under goexperiment.jsonv2 X-Git-Tag: go1.26rc1~621 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=ee5af46172e64eceddb56018de8ea850fe0a6cae;p=gostls13.git 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 Reviewed-by: Dmitri Shuralyov Reviewed-by: Damien Neil --- diff --git a/src/encoding/json/v2_decode.go b/src/encoding/json/v2_decode.go index 1041ec7ee4..f17d7ebcca 100644 --- a/src/encoding/json/v2_decode.go +++ b/src/encoding/json/v2_decode.go @@ -14,6 +14,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "encoding/json/internal/jsonwire" "encoding/json/jsontext" @@ -119,7 +120,20 @@ type UnmarshalTypeError struct { func (e *UnmarshalTypeError) Error() string { var s string if e.Struct != "" || e.Field != "" { - s = "json: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String() + // The design of UnmarshalTypeError overly assumes a struct-based + // Go representation for the JSON value. + // The logic in jsontext represents paths using a JSON Pointer, + // which is agnostic to the Go type system. + // Trying to convert a JSON Pointer into a UnmarshalTypeError.Field + // is difficult. As a heuristic, if the last path token looks like + // an index into a JSON array (e.g., ".foo.bar.0"), + // avoid the phrase "Go struct field ". + intoWhat := "Go struct field " + i := strings.LastIndexByte(e.Field, '.') + len(".") + if len(e.Field[i:]) > 0 && strings.TrimRight(e.Field[i:], "0123456789") == "" { + intoWhat = "" // likely a Go slice or array + } + s = "json: cannot unmarshal " + e.Value + " into " + intoWhat + e.Struct + "." + e.Field + " of type " + e.Type.String() } else { s = "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() } diff --git a/src/encoding/json/v2_decode_test.go b/src/encoding/json/v2_decode_test.go index 28c57ec8bf..26b4448721 100644 --- a/src/encoding/json/v2_decode_test.go +++ b/src/encoding/json/v2_decode_test.go @@ -2363,6 +2363,34 @@ func TestUnmarshalTypeError(t *testing.T) { } } +func TestUnmarshalTypeErrorMessage(t *testing.T) { + err := &UnmarshalTypeError{ + Value: "number 5", + Type: reflect.TypeFor[int](), + Offset: 1234, + Struct: "Root", + } + + for _, tt := range []struct { + field string + want string + }{ + {"", "json: cannot unmarshal number 5 into Go struct field Root. of type int"}, + {"1", "json: cannot unmarshal number 5 into Root.1 of type int"}, + {"foo", "json: cannot unmarshal number 5 into Go struct field Root.foo of type int"}, + {"foo.1", "json: cannot unmarshal number 5 into Root.foo.1 of type int"}, + {"foo.bar", "json: cannot unmarshal number 5 into Go struct field Root.foo.bar of type int"}, + {"foo.bar.1", "json: cannot unmarshal number 5 into Root.foo.bar.1 of type int"}, + {"foo.bar.baz", "json: cannot unmarshal number 5 into Go struct field Root.foo.bar.baz of type int"}, + } { + err.Field = tt.field + got := err.Error() + if got != tt.want { + t.Errorf("Error:\n\tgot: %v\n\twant: %v", got, tt.want) + } + } +} + func TestUnmarshalSyntax(t *testing.T) { var x any tests := []struct {