encoding/json: report true from v2 Decoder.More when an error is pending

Historically, Decoder.More reports true when the next read will
return an error. Adjust the v2 Decoder to follow this behavior.

Fixes #76467

Change-Id: I03bfa391e4e89ada8ca869db43c1d0bb63cc0413
Reviewed-on: https://go-review.googlesource.com/c/go/+/728300
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Damien Neil 2025-12-08 13:09:05 -08:00 committed by Gopher Robot
parent 7b60d06739
commit 00642ee23b
3 changed files with 41 additions and 1 deletions

View File

@ -459,6 +459,9 @@ func TestDecodeInStream(t *testing.T) {
{CaseName: Name(""), json: ` \a`, expTokens: []any{
&SyntaxError{"invalid character '\\\\' looking for beginning of value", 1},
}},
{CaseName: Name(""), json: `,`, expTokens: []any{
&SyntaxError{"invalid character ',' looking for beginning of value", 0},
}},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
@ -467,6 +470,15 @@ func TestDecodeInStream(t *testing.T) {
var got any
var err error
wantMore := true
switch want {
case Delim(']'), Delim('}'):
wantMore = false
}
if got := dec.More(); got != wantMore {
t.Fatalf("%s:\n\tinput: %s\n\tdec.More() = %v, want %v (next token: %T(%v))", tt.Where, tt.json, got, wantMore, want, want)
}
if dt, ok := want.(decodeThis); ok {
want = dt.v
err = dec.Decode(&got)

View File

@ -197,6 +197,9 @@ func (d Delim) String() string {
// to mark the start and end of arrays and objects.
// Commas and colons are elided.
func (dec *Decoder) Token() (Token, error) {
if dec.err != nil {
return nil, dec.err
}
tok, err := dec.dec.ReadToken()
if err != nil {
// Historically, v1 would report just [io.EOF] if
@ -238,7 +241,20 @@ func (dec *Decoder) Token() (Token, error) {
func (dec *Decoder) More() bool {
dec.hadPeeked = true
k := dec.dec.PeekKind()
return k > 0 && k != ']' && k != '}'
if k == 0 {
if dec.err == nil {
// PeekKind doesn't distinguish between EOF and error,
// so read the next token to see which we get.
_, err := dec.dec.ReadToken()
if err == nil {
// This is only possible if jsontext violates its documentation.
err = errors.New("json: successful read after failed peek")
}
dec.err = transformSyntacticError(err)
}
return dec.err != io.EOF
}
return k != ']' && k != '}'
}
// InputOffset returns the input stream byte offset of the current decoder position.

View File

@ -439,6 +439,9 @@ func TestDecodeInStream(t *testing.T) {
{CaseName: Name(""), json: ` \a`, expTokens: []any{
&SyntaxError{"invalid character '\\\\' looking for beginning of value", len64(` `)},
}},
{CaseName: Name(""), json: `,`, expTokens: []any{
&SyntaxError{"invalid character ',' looking for beginning of value", 0},
}},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
@ -447,6 +450,15 @@ func TestDecodeInStream(t *testing.T) {
var got any
var err error
wantMore := true
switch want {
case Delim(']'), Delim('}'):
wantMore = false
}
if got := dec.More(); got != wantMore {
t.Fatalf("%s:\n\tinput: %s\n\tdec.More() = %v, want %v (next token: %T(%v)) rem:%q", tt.Where, tt.json, got, wantMore, want, want, tt.json[dec.InputOffset():])
}
if dt, ok := want.(decodeThis); ok {
want = dt.v
err = dec.Decode(&got)