When GOEXPERIMENT=jsonv2, json.UnmarshalTypeError errors have gratuitous error text changes. While we don't guarantee stability of error text, json/v2 goes to some effort to keep errors stable where possible to minimize the impact on existing code and we probably can do better here.

// v1: json: cannot unmarshal number into Go value of type chan int
// v2: json: cannot unmarshal into Go type chan int
var v chan int
text := `1`
err := json.Unmarshal([]byte(text), &v)
// v1: json: cannot unmarshal number into Go value of type error
// v2: json: cannot unmarshal into Go type error: cannot derive concrete type for nil interface with finite type set
var v error
text := `1`
err := json.Unmarshal([]byte(text), &v)
// v1: json: cannot unmarshal string into Go struct field .F.V of type int
// v2: json: cannot unmarshal JSON string into T.F.V of Go type int
type T struct{ F struct{ V int } }
var v T
text := `{"F":{"V":"s"}}`
err := json.Unmarshal([]byte(text), &v)

Comment From: gabyhelp

Related Issues

Related Code Changes

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: gopherbot

Change https://go.dev/cl/689918 mentions this issue: encoding/json: reduce error text regressions under goexperiment.jsonv2

Comment From: dsnet

Okay, while my CL will improve the error text, it doesn't "fix" the reporting of struct fields, which has always been broken in v1 and I don't feel motivated to replicate the broken behavior in v2.

Consider the following:

type Nested struct{ F2 int }
type RootNamed struct{ F1 Nested }
type RootUnnamed struct{ F1 struct{ F2 int } }
fmt.Println(json.Unmarshal([]byte(`{"F1":true}`),        new(RootNamed)))
fmt.Println(json.Unmarshal([]byte(`{"F1":{"F2":true}}`), new(RootNamed)))
fmt.Println(json.Unmarshal([]byte(`{"F1":true}`),        new(RootUnnamed)))
fmt.Println(json.Unmarshal([]byte(`{"F1":{"F2":true}}`), new(RootUnnamed)))

This prints:

json: cannot unmarshal bool into Go struct field RootNamed.F1 of type main.Nested
json: cannot unmarshal bool into Go struct field Nested.F1.F2 of type int
json: cannot unmarshal bool into Go struct field RootUnnamed.F1 of type struct { F2 int }
json: cannot unmarshal bool into Go struct field .F1.F2 of type int

Notice that the "root" struct type that it uses is the struct type immediately preceding the last field. Thus, you end up with silly outputs like Nested.F1.F2 which doesn't make sense since there is no F1 field in the Nested type.

Comment From: dsnet

Oh fun, I reported this issue 5 years ago in #43126. 😅