diff --git a/src/encoding/json/v2/arshal.go b/src/encoding/json/v2/arshal.go index e2ce778d5ad..6b4bcb0c74c 100644 --- a/src/encoding/json/v2/arshal.go +++ b/src/encoding/json/v2/arshal.go @@ -470,7 +470,7 @@ func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct, last bo // was validated before attempting to unmarshal it. if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { if err := export.Decoder(in).CheckNextValue(last); err != nil { - if err == io.EOF { + if err == io.EOF && last { offset := in.InputOffset() + int64(len(in.UnreadBuffer())) return &jsontext.SyntacticError{ByteOffset: offset, Err: io.ErrUnexpectedEOF} } @@ -487,7 +487,7 @@ func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct, last bo if !uo.Flags.Get(jsonflags.AllowDuplicateNames) { export.Decoder(in).Tokens.InvalidateDisabledNamespaces() } - if err == io.EOF { + if err == io.EOF && last { offset := in.InputOffset() + int64(len(in.UnreadBuffer())) return &jsontext.SyntacticError{ByteOffset: offset, Err: io.ErrUnexpectedEOF} } diff --git a/src/encoding/json/v2/arshal_test.go b/src/encoding/json/v2/arshal_test.go index f1ee2e2e3a7..764ce690078 100644 --- a/src/encoding/json/v2/arshal_test.go +++ b/src/encoding/json/v2/arshal_test.go @@ -9413,6 +9413,51 @@ func TestUnmarshalDecodeOptions(t *testing.T) { } } +func TestUnmarshalDecodeStream(t *testing.T) { + tests := []struct { + in string + want []any + err error + }{ + {in: ``, err: io.EOF}, + {in: `{`, err: &jsontext.SyntacticError{ByteOffset: len64(`{`), Err: io.ErrUnexpectedEOF}}, + {in: `{"`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"`), Err: io.ErrUnexpectedEOF}}, + {in: `{"k"`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"k"`), JSONPointer: "/k", Err: io.ErrUnexpectedEOF}}, + {in: `{"k":`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"k":`), JSONPointer: "/k", Err: io.ErrUnexpectedEOF}}, + {in: `{"k",`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"k"`), JSONPointer: "/k", Err: jsonwire.NewInvalidCharacterError(",", "after object name (expecting ':')")}}, + {in: `{"k"}`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"k"`), JSONPointer: "/k", Err: jsonwire.NewInvalidCharacterError("}", "after object name (expecting ':')")}}, + {in: `[`, err: &jsontext.SyntacticError{ByteOffset: len64(`[`), Err: io.ErrUnexpectedEOF}}, + {in: `[0`, err: &jsontext.SyntacticError{ByteOffset: len64(`[0`), Err: io.ErrUnexpectedEOF}}, + {in: ` [0`, err: &jsontext.SyntacticError{ByteOffset: len64(` [0`), Err: io.ErrUnexpectedEOF}}, + {in: `[0.`, err: &jsontext.SyntacticError{ByteOffset: len64(`[`), JSONPointer: "/0", Err: io.ErrUnexpectedEOF}}, + {in: `[0. `, err: &jsontext.SyntacticError{ByteOffset: len64(`[0.`), JSONPointer: "/0", Err: jsonwire.NewInvalidCharacterError(" ", "in number (expecting digit)")}}, + {in: `[0,`, err: &jsontext.SyntacticError{ByteOffset: len64(`[0,`), Err: io.ErrUnexpectedEOF}}, + {in: `[0:`, err: &jsontext.SyntacticError{ByteOffset: len64(`[0`), Err: jsonwire.NewInvalidCharacterError(":", "after array element (expecting ',' or ']')")}}, + {in: `n`, err: &jsontext.SyntacticError{ByteOffset: len64(`n`), Err: io.ErrUnexpectedEOF}}, + {in: `nul`, err: &jsontext.SyntacticError{ByteOffset: len64(`nul`), Err: io.ErrUnexpectedEOF}}, + {in: `fal `, err: &jsontext.SyntacticError{ByteOffset: len64(`fal`), Err: jsonwire.NewInvalidCharacterError(" ", "in literal false (expecting 's')")}}, + {in: `false`, want: []any{false}, err: io.EOF}, + {in: `false0.0[]null`, want: []any{false, 0.0, []any{}, nil}, err: io.EOF}, + } + for _, tt := range tests { + d := jsontext.NewDecoder(strings.NewReader(tt.in)) + var got []any + for { + var v any + if err := UnmarshalDecode(d, &v); err != nil { + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("`%s`: UnmarshalDecode error = %v, want %v", tt.in, err, tt.err) + } + break + } + got = append(got, v) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("`%s`: UnmarshalDecode = %v, want %v", tt.in, got, tt.want) + } + } +} + // BenchmarkUnmarshalDecodeOptions is a minimal decode operation to measure // the overhead options setup before the unmarshal operation. func BenchmarkUnmarshalDecodeOptions(b *testing.B) {