@@ -45,7 +45,18 @@ internal abstract class SequentialJsonEnumerator<T>(
4545 where T : struct, IJsonValue<T>
4646{
4747 private PipeReader PipeReader { get; } = PipeReader.Create(stream);
48+
49+ /// <summary>
50+ /// Delimiter between each item
51+ /// </summary>
4852 protected abstract byte Delimiter { get; }
53+
54+ /// <summary>
55+ /// Does the sequence require ending with a delimiter?
56+ /// </summary>
57+ protected abstract bool RequiresDelimiterAfterLastItem { get; }
58+
59+ /// <inheritdoc/>
4960 public ValueTask DisposeAsync() => PipeReader.CompleteAsync();
5061
5162 private int _itemPosition = -1;
@@ -71,13 +82,19 @@ public async ValueTask<bool> MoveNextAsync()
7182 return true;
7283 }
7384
74- if (result.IsCompleted)
85+ switch (result.IsCompleted)
7586 {
76- PipeReader.AdvanceTo(buffer.End);
77- return false;
87+ case true when buffer.IsEmpty:
88+ return false;
89+ case true when !RequiresDelimiterAfterLastItem:
90+ _itemPosition++;
91+ Current = ParseItem(buffer);
92+ PipeReader.AdvanceTo(buffer.End);
93+ return true;
94+ default:
95+ PipeReader.AdvanceTo(buffer.Start, buffer.End);
96+ break;
7897 }
79-
80- PipeReader.AdvanceTo(buffer.Start, buffer.End);
8198 } while (true);
8299 }
83100
@@ -122,6 +139,7 @@ internal sealed class ApplicationJsonlEnumerator<T>(Stream stream, CancellationT
122139 where T : struct, IJsonValue<T>
123140{
124141 protected override byte Delimiter => 0x0A;
142+ protected override bool RequiresDelimiterAfterLastItem => false;
125143 protected override T ParseItem(ReadOnlySequence<byte> data) => T.Parse(data);
126144}
127145
@@ -134,6 +152,7 @@ internal sealed class ApplicationJsonSeqEnumerator<T>(Stream stream, Cancellatio
134152{
135153 private const byte RecordSeparator = 0x1E;
136154 protected override byte Delimiter => 0x0A;
155+ protected override bool RequiresDelimiterAfterLastItem => true;
137156
138157 protected override T ParseItem(ReadOnlySequence<byte> data)
139158 {
0 commit comments