Skip to content

Commit a6eec8b

Browse files
dsnetgopherbot
authored andcommitted
encoding/json: reduce error text regressions under goexperiment.jsonv2
There were minor and unnecessary error text changes when v1 was implemented using v2. Reduce divergences if possible. Of the cases reported in #74713, there are no more differences for: v1: json: cannot unmarshal number into Go value of type chan int v2: json: cannot unmarshal number into Go value of type chan int and v1: json: cannot unmarshal number into Go value of type error v2: json: cannot unmarshal number into Go value of type error However, there is a difference between: v1: json: cannot unmarshal string into Go struct field .F.V of type int v2: json: cannot unmarshal string into Go struct field S.F.V of type int For reasons unclear, the v1 logic was always inconsistent about whether it could properly record the root struct type, while the v1 emulation layer under v2 is always able to. This only modifies code that is compiled in under goexperiment.jsonv2. Fixes #74713 Change-Id: I9e87323b1810130cb929288fdd86aff4be82d5f2 Reviewed-on: https://go-review.googlesource.com/c/go/+/689918 Reviewed-by: Damien Neil <[email protected]> Auto-Submit: Joseph Tsai <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent 0fa88de commit a6eec8b

File tree

8 files changed

+76
-25
lines changed

8 files changed

+76
-25
lines changed

src/encoding/json/decode_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ type DoublePtr struct {
416416
J **int
417417
}
418418

419+
type NestedUnamed struct{ F struct{ V int } }
420+
419421
var unmarshalTests = []struct {
420422
CaseName
421423
in string
@@ -1213,6 +1215,28 @@ var unmarshalTests = []struct {
12131215
F string `json:"-,omitempty"`
12141216
}{"hello"},
12151217
},
1218+
1219+
{
1220+
CaseName: Name("ErrorForNestedUnamed"),
1221+
in: `{"F":{"V":"s"}}`,
1222+
ptr: new(NestedUnamed),
1223+
out: NestedUnamed{},
1224+
err: &UnmarshalTypeError{Value: "string", Type: reflect.TypeFor[int](), Offset: 13, Field: "F.V"},
1225+
},
1226+
{
1227+
CaseName: Name("ErrorInterface"),
1228+
in: `1`,
1229+
ptr: new(error),
1230+
out: error(nil),
1231+
err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[error](), Offset: 1},
1232+
},
1233+
{
1234+
CaseName: Name("ErrorChan"),
1235+
in: `1`,
1236+
ptr: new(chan int),
1237+
out: (chan int)(nil),
1238+
err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[chan int](), Offset: 1},
1239+
},
12161240
}
12171241

12181242
func TestMarshal(t *testing.T) {

src/encoding/json/internal/internal.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var AllowInternalUse NotForPublicUse
2121
var (
2222
ErrCycle = errors.New("encountered a cycle")
2323
ErrNonNilReference = errors.New("value must be passed as a non-nil pointer reference")
24+
ErrNilInterface = errors.New("cannot derive concrete type for nil interface with finite type set")
2425
)
2526

2627
var (

src/encoding/json/v2/arshal_default.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,8 +1690,6 @@ func makePointerArshaler(t reflect.Type) *arshaler {
16901690
return &fncs
16911691
}
16921692

1693-
var errNilInterface = errors.New("cannot derive concrete type for nil interface with finite type set")
1694-
16951693
func makeInterfaceArshaler(t reflect.Type) *arshaler {
16961694
// NOTE: Values retrieved from an interface are not addressable,
16971695
// so we shallow copy the values to make them addressable and
@@ -1797,7 +1795,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler {
17971795

17981796
k := dec.PeekKind()
17991797
if !isAnyType(t) {
1800-
return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, errNilInterface)
1798+
return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, internal.ErrNilInterface)
18011799
}
18021800
switch k {
18031801
case 'f', 't':

src/encoding/json/v2/arshal_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7496,7 +7496,7 @@ func TestUnmarshal(t *testing.T) {
74967496
inBuf: `"hello"`,
74977497
inVal: new(io.Reader),
74987498
want: new(io.Reader),
7499-
wantErr: EU(errNilInterface).withType(0, T[io.Reader]()),
7499+
wantErr: EU(internal.ErrNilInterface).withType(0, T[io.Reader]()),
75007500
}, {
75017501
name: jsontest.Name("Interfaces/Empty/False"),
75027502
inBuf: `false`,
@@ -8344,7 +8344,7 @@ func TestUnmarshal(t *testing.T) {
83448344
inBuf: `{"X":"hello"}`,
83458345
inVal: addr(struct{ X fmt.Stringer }{nil}),
83468346
want: addr(struct{ X fmt.Stringer }{nil}),
8347-
wantErr: EU(errNilInterface).withPos(`{"X":`, "/X").withType(0, T[fmt.Stringer]()),
8347+
wantErr: EU(internal.ErrNilInterface).withPos(`{"X":`, "/X").withType(0, T[fmt.Stringer]()),
83488348
}, {
83498349
name: jsontest.Name("Functions/Interface/NetIP"),
83508350
opts: []Options{

src/encoding/json/v2/errors.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,17 @@ func newMarshalErrorBefore(e *jsontext.Encoder, t reflect.Type, err error) error
120120
// is positioned right before the next token or value, which causes an error.
121121
// It does not record the next JSON kind as this error is used to indicate
122122
// the receiving Go value is invalid to unmarshal into (and not a JSON error).
123+
// However, if [jsonflags.ReportErrorsWithLegacySemantics] is specified,
124+
// then it does record the next JSON kind for historical reporting reasons.
123125
func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) error {
126+
var k jsontext.Kind
127+
if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
128+
k = d.PeekKind()
129+
}
124130
return &SemanticError{action: "unmarshal", GoType: t, Err: err,
125131
ByteOffset: d.InputOffset() + int64(export.Decoder(d).CountNextDelimWhitespace()),
126-
JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1))}
132+
JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1)),
133+
JSONKind: k}
127134
}
128135

129136
// newUnmarshalErrorBeforeWithSkipping is like [newUnmarshalErrorBefore],

src/encoding/json/v2_decode.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,19 +117,11 @@ type UnmarshalTypeError struct {
117117
}
118118

119119
func (e *UnmarshalTypeError) Error() string {
120-
s := "json: cannot unmarshal"
121-
if e.Value != "" {
122-
s += " JSON " + e.Value
123-
}
124-
s += " into"
125-
var preposition string
126-
if e.Field != "" {
127-
s += " " + e.Struct + "." + e.Field
128-
preposition = " of"
129-
}
130-
if e.Type != nil {
131-
s += preposition
132-
s += " Go type " + e.Type.String()
120+
var s string
121+
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+
} else {
124+
s = "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
133125
}
134126
if e.Err != nil {
135127
s += ": " + e.Err.Error()

src/encoding/json/v2_decode_test.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ type DoublePtr struct {
420420
J **int
421421
}
422422

423+
type NestedUnamed struct{ F struct{ V int } }
424+
423425
var unmarshalTests = []struct {
424426
CaseName
425427
in string
@@ -1219,6 +1221,28 @@ var unmarshalTests = []struct {
12191221
F string `json:"-,omitempty"`
12201222
}{"hello"},
12211223
},
1224+
1225+
{
1226+
CaseName: Name("ErrorForNestedUnamed"),
1227+
in: `{"F":{"V":"s"}}`,
1228+
ptr: new(NestedUnamed),
1229+
out: NestedUnamed{},
1230+
err: &UnmarshalTypeError{Value: "string", Type: reflect.TypeFor[int](), Offset: 10, Struct: "NestedUnamed", Field: "F.V"},
1231+
},
1232+
{
1233+
CaseName: Name("ErrorInterface"),
1234+
in: `1`,
1235+
ptr: new(error),
1236+
out: error(nil),
1237+
err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[error]()},
1238+
},
1239+
{
1240+
CaseName: Name("ErrorChan"),
1241+
in: `1`,
1242+
ptr: new(chan int),
1243+
out: (chan int)(nil),
1244+
err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[chan int]()},
1245+
},
12221246
}
12231247

12241248
func TestMarshal(t *testing.T) {
@@ -1552,12 +1576,12 @@ func TestErrorMessageFromMisusedString(t *testing.T) {
15521576
CaseName
15531577
in, err string
15541578
}{
1555-
{Name(""), `{"result":"x"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character 'x' looking for beginning of object key string`},
1556-
{Name(""), `{"result":"foo"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character 'f' looking for beginning of object key string`},
1557-
{Name(""), `{"result":"123"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character '1' looking for beginning of object key string`},
1558-
{Name(""), `{"result":123}`, `json: cannot unmarshal JSON number into WrongString.result of Go type string`},
1559-
{Name(""), `{"result":"\""}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: unexpected end of JSON input`},
1560-
{Name(""), `{"result":"\"foo"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: unexpected end of JSON input`},
1579+
{Name(""), `{"result":"x"}`, `json: cannot unmarshal string into Go struct field WrongString.result of type string: invalid character 'x' looking for beginning of object key string`},
1580+
{Name(""), `{"result":"foo"}`, `json: cannot unmarshal string into Go struct field WrongString.result of type string: invalid character 'f' looking for beginning of object key string`},
1581+
{Name(""), `{"result":"123"}`, `json: cannot unmarshal string into Go struct field WrongString.result of type string: invalid character '1' looking for beginning of object key string`},
1582+
{Name(""), `{"result":123}`, `json: cannot unmarshal number into Go struct field WrongString.result of type string`},
1583+
{Name(""), `{"result":"\""}`, `json: cannot unmarshal string into Go struct field WrongString.result of type string: unexpected end of JSON input`},
1584+
{Name(""), `{"result":"\"foo"}`, `json: cannot unmarshal string into Go struct field WrongString.result of type string: unexpected end of JSON input`},
15611585
}
15621586
for _, tt := range tests {
15631587
t.Run(tt.Name, func(t *testing.T) {
@@ -2545,6 +2569,7 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) {
25452569
ptr: new(S1),
25462570
out: &S1{R: 2},
25472571
err: &UnmarshalTypeError{
2572+
Value: "number",
25482573
Type: reflect.TypeFor[S1](),
25492574
Offset: len64(`{"R":2,"Q":`),
25502575
Struct: "S1",
@@ -2577,6 +2602,7 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) {
25772602
ptr: new(S5),
25782603
out: &S5{R: 2},
25792604
err: &UnmarshalTypeError{
2605+
Value: "number",
25802606
Type: reflect.TypeFor[S5](),
25812607
Offset: len64(`{"R":2,"Q":`),
25822608
Struct: "S5",

src/encoding/json/v2_inject.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ func transformUnmarshalError(root any, err error) error {
7373
if err.Err == jsonv2.ErrUnknownName {
7474
return fmt.Errorf("json: unknown field %q", err.JSONPointer.LastToken())
7575
}
76+
if err.Err == internal.ErrNilInterface {
77+
err.Err = nil // non-descriptive for historical reasons
78+
}
7679

7780
// Historically, UnmarshalTypeError has always been inconsistent
7881
// about how it reported position information.

0 commit comments

Comments
 (0)