From 192165177175f591f10bb12f6985c54a594824f0 Mon Sep 17 00:00:00 2001 From: omerbenda Date: Tue, 16 Dec 2025 18:27:41 +0200 Subject: [PATCH] fix(CsvReader.cs): Add a read method to read without skipping headers --- src/CsvHelper/CsvReader.cs | 96 +++++++++++++------ .../Reading/ShouldSkipRecordTests.cs | 31 ++++++ 2 files changed, 98 insertions(+), 29 deletions(-) diff --git a/src/CsvHelper/CsvReader.cs b/src/CsvHelper/CsvReader.cs index 3fa74c2a0..f5fac97e3 100644 --- a/src/CsvHelper/CsvReader.cs +++ b/src/CsvHelper/CsvReader.cs @@ -237,6 +237,50 @@ protected virtual void ValidateHeader(ClassMap map, List invalidH } } + /// + /// Advances the reader to the header record. Does not skip over records and should not be used to read records. + /// You need to call then + /// for the headers to be read. + /// + /// True if there are more records, otherwise false. + public virtual bool PreHeaderRead() + { + // Don't forget about the async method below! + + bool hasMoreData = parser.Read(); + hasBeenRead = true; + + currentIndex = -1; + + if (detectColumnCountChanges && hasMoreData) + { + UpdateColumnCount(); + } + + return hasMoreData; + } + + /// + /// Advances the reader to the header record. Does not skip over records and should not be used to read records. + /// You need to call then + /// for the headers to be read. + /// + /// True if there are more records, otherwise false. + public virtual async Task PreHeaderReadAsync() + { + bool hasMoreData = await parser.ReadAsync().ConfigureAwait(false); + hasBeenRead = true; + + currentIndex = -1; + + if (detectColumnCountChanges && hasMoreData) + { + UpdateColumnCount(); + } + + return hasMoreData; + } + /// public virtual bool Read() { @@ -254,18 +298,7 @@ public virtual bool Read() if (detectColumnCountChanges && hasMoreRecords) { - if (prevColumnCount > 0 && prevColumnCount != parser.Count) - { - var csvException = new BadDataException(string.Empty, parser.RawRecord, context, "An inconsistent number of columns has been detected."); - - var args = new ReadingExceptionOccurredArgs(csvException); - if (readingExceptionOccurred?.Invoke(args) ?? true) - { - throw csvException; - } - } - - prevColumnCount = parser.Count; + UpdateColumnCount(); } return hasMoreRecords; @@ -286,21 +319,26 @@ public virtual async Task ReadAsync() if (detectColumnCountChanges && hasMoreRecords) { - if (prevColumnCount > 0 && prevColumnCount != parser.Count) - { - var csvException = new BadDataException(string.Empty, parser.RawRecord, context, "An inconsistent number of columns has been detected."); + UpdateColumnCount(); + } - var args = new ReadingExceptionOccurredArgs(csvException); - if (readingExceptionOccurred?.Invoke(args) ?? true) - { - throw csvException; - } - } + return hasMoreRecords; + } + + private void UpdateColumnCount() + { + if (prevColumnCount > 0 && prevColumnCount != parser.Count) + { + var csvException = new BadDataException(string.Empty, parser.RawRecord, context, "An inconsistent number of columns has been detected."); - prevColumnCount = parser.Count; + var args = new ReadingExceptionOccurredArgs(csvException); + if (readingExceptionOccurred?.Invoke(args) ?? true) + { + throw csvException; + } } - return hasMoreRecords; + prevColumnCount = parser.Count; } /// @@ -843,7 +881,7 @@ public virtual IEnumerable GetRecords() if (hasHeaderRecord && headerRecord == null) { - if (!Read()) + if (!PreHeaderRead()) { yield break; } @@ -924,7 +962,7 @@ public virtual IEnumerable GetRecords(Type type) if (hasHeaderRecord && headerRecord == null) { - if (!Read()) + if (!PreHeaderRead()) { yield break; } @@ -989,7 +1027,7 @@ public virtual IEnumerable EnumerateRecords(T record) if (hasHeaderRecord && headerRecord == null) { - if (!Read()) + if (!PreHeaderRead()) { yield break; } @@ -1046,7 +1084,7 @@ public virtual IEnumerable EnumerateRecords(T record) if (hasHeaderRecord && headerRecord == null) { - if (!await ReadAsync().ConfigureAwait(false)) + if (!await PreHeaderReadAsync().ConfigureAwait(false)) { yield break; } @@ -1128,7 +1166,7 @@ public virtual async IAsyncEnumerable GetRecordsAsync(Type type, [Enumer if (hasHeaderRecord && headerRecord == null) { - if (!await ReadAsync().ConfigureAwait(false)) + if (!await PreHeaderReadAsync().ConfigureAwait(false)) { yield break; } @@ -1194,7 +1232,7 @@ public virtual async IAsyncEnumerable EnumerateRecordsAsync(T record, [Enu if (hasHeaderRecord && headerRecord == null) { - if (!await ReadAsync().ConfigureAwait(false)) + if (!await PreHeaderReadAsync().ConfigureAwait(false)) { yield break; } diff --git a/tests/CsvHelper.Tests/Reading/ShouldSkipRecordTests.cs b/tests/CsvHelper.Tests/Reading/ShouldSkipRecordTests.cs index 893785dbd..045a18c97 100644 --- a/tests/CsvHelper.Tests/Reading/ShouldSkipRecordTests.cs +++ b/tests/CsvHelper.Tests/Reading/ShouldSkipRecordTests.cs @@ -83,5 +83,36 @@ public void ShouldSkipWithEmptyRows() Assert.Equal("1", csv.GetField(0)); Assert.Equal("2", csv.GetField(1)); } + + [Fact] + public void ShouldSkipIgnoreHeader() + { + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + ShouldSkipRecord = skipArgs => !skipArgs.Row[0]!.StartsWith("A") + }; + + var parser = new ParserMock(config) + { + { "Name" }, + { "Arnold" }, + { "Andrew" }, + { "Betty" }, + { "Frank" } + }; + + var csv = new CsvReader(parser); + var csvReader = new CsvReader(parser); + var typeDef = new + { + Name = string.Empty + }; + + var records = csvReader.GetRecords(typeDef).ToList(); + + Assert.Equal(2, records.Count); + Assert.Equal("Arnold", records[0].Name); + Assert.Equal("Andrew", records[1].Name); + } } }