From 00642ee23b614d5314604b6b4d25c671f82c26b2 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Mon, 8 Dec 2025 13:09:05 -0800 Subject: [PATCH] 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 Auto-Submit: Damien Neil Reviewed-by: Joseph Tsai LUCI-TryBot-Result: Go LUCI --- src/encoding/json/stream_test.go | 12 ++++++++++++ src/encoding/json/v2_stream.go | 18 +++++++++++++++++- src/encoding/json/v2_stream_test.go | 12 ++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/encoding/json/stream_test.go b/src/encoding/json/stream_test.go index 0e937cfaa1..ba3bc4002b 100644 --- a/src/encoding/json/stream_test.go +++ b/src/encoding/json/stream_test.go @@ -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) diff --git a/src/encoding/json/v2_stream.go b/src/encoding/json/v2_stream.go index dcee553ee1..ca0822cb73 100644 --- a/src/encoding/json/v2_stream.go +++ b/src/encoding/json/v2_stream.go @@ -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. diff --git a/src/encoding/json/v2_stream_test.go b/src/encoding/json/v2_stream_test.go index 0885631fb5..d7f9f11084 100644 --- a/src/encoding/json/v2_stream_test.go +++ b/src/encoding/json/v2_stream_test.go @@ -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) -- 2.52.0