Skip to content

Commit 4eca7e0

Browse files
committed
Csv: add FillMissingColumnsWithNull option (issue #979)
Rows with fewer columns than the header used to throw ColumnNotFoundException, which blocked processing files with trailing optional fields. Adds a CsvConfiguration option that pads missing columns with null instead, keeping the default behavior unchanged.
1 parent a78fe0c commit 4eca7e0

3 files changed

Lines changed: 32 additions & 1 deletion

File tree

‎src/MiniExcel.Csv/CsvConfiguration.cs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ public class CsvConfiguration : MiniExcelBaseConfiguration
1212
public string NewLine { get; set; } = "\r\n";
1313
public bool ReadLineBreaksWithinQuotes { get; set; } = true;
1414
public bool ReadEmptyStringAsNull { get; set; } = false;
15+
16+
/// <summary>
17+
/// When true, rows with fewer columns than the header are padded with null values
18+
/// instead of throwing a ColumnNotFoundException. Default is false.
19+
/// </summary>
20+
public bool FillMissingColumnsWithNull { get; set; } = false;
1521
public bool AlwaysQuote { get; set; } = false;
1622
public bool QuoteWhitespaces { get; set; } = true;
1723
public Func<string, string[]>? SplitFn { get; set; }

‎src/MiniExcel.Csv/CsvReader.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ internal CsvReader(Stream stream, IMiniExcelConfiguration? configuration, bool l
6767
var read = Split(finalRow);
6868

6969
// invalid row check
70-
if (read.Length < headRows.Count)
70+
if (read.Length < headRows.Count && !_config.FillMissingColumnsWithNull)
7171
{
7272
var colIndex = read.Length;
7373
var headers = headRows.ToDictionary(x => x.Value, x => x.Key);

‎tests/MiniExcel.Csv.Tests/Issues/GithubIssuesTests.cs‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,31 @@ public class GithubIssuesTests
88
private readonly OpenXmlExporter _openXmlExporter = MiniExcel.Exporters.GetOpenXmlExporter();
99
private readonly OpenXmlImporter _openXmlImporter = MiniExcel.Importers.GetOpenXmlImporter();
1010

11+
[Fact]
12+
public async Task Issue979()
13+
{
14+
const string text = "A,B,C\n1,2\n";
15+
using var path = AutoDeletingPath.Create(ExcelType.Csv);
16+
File.WriteAllText(path.ToString(), text);
17+
18+
// default behavior: a row with fewer columns than the header throws
19+
await Assert.ThrowsAsync<ColumnNotFoundException>(async () =>
20+
{
21+
await foreach (var _ in _csvImporter.QueryAsync(path.ToString(), hasHeaderRow: true)) { }
22+
});
23+
24+
// with FillMissingColumnsWithNull, missing columns are padded with null
25+
var config = new CsvConfiguration { FillMissingColumnsWithNull = true };
26+
var rows = new List<dynamic>();
27+
await foreach (var row in _csvImporter.QueryAsync(path.ToString(), hasHeaderRow: true, configuration: config))
28+
rows.Add(row);
29+
30+
var casted = (IDictionary<string, object?>)rows[0];
31+
Assert.Equal("1", casted["A"]?.ToString());
32+
Assert.Equal("2", casted["B"]?.ToString());
33+
Assert.Null(casted["C"]);
34+
}
35+
1136
// Support for Enum Mapping
1237
[Fact]
1338
public void Issue89()

0 commit comments

Comments
 (0)