-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Describe the bug
I tried to read a CSV file whereas starting from the third column all following columns should be mapped to the same List<int?>. At first, I used the below code without ClassMap and just used property attributes (see below).
This resulted in the first two columns being read and mapped correctly, but List<int?>? TagIds was still null, althought the CSV file had values in columns 3 and following. So, the attribute I added to that property ([Index(2, indexEnd: -1)]) is unfortunately not working (as I expected).
Later, I added that ClassMap, which has the same details as the property attributes. When executing csvReader.Context.RegisterClassMap<ExampleCsv.ExampleCsvMap>(); before reading that CSV file, it works and List<int?>? TagIds gets populated with the correct values (the two other props as well, so everything just fine).
Why do both approaches lead to different results? (Seems like a bug to me.)
To Reproduce
I used .NET 9.0 and CsvHelper v33.1.0
Execute this code, to reproduce the bug:
using var reader = new StreamReader(stream, CsvHlpr.DEFAULT_ENCODING_FOR_CSV_IMPORTS);
using var csvReader = CsvHlpr.GetCsvReader(reader);
// Note: Not registering any ClassMap here.
await foreach (var record in csvReader.GetRecordsAsync<ExampleCsv>())
{
// Do something...
}Execute this code to make use of the ClassMap (where the bug is not occuring):
using var reader = new StreamReader(stream, CsvHlpr.DEFAULT_ENCODING_FOR_CSV_IMPORTS);
using var csvReader = CsvHlpr.GetCsvReader(reader);
csvReader.Context.RegisterClassMap<ExampleCsv.ExampleCsvMap>();
await foreach (var record in csvReader.GetRecordsAsync<ExampleCsv>())
{
// Do something...
}Additional Code:
using System.Globalization;
using System.Text;
using CsvHelper;
using CsvHelper.Configuration;
using CsvHelper.Configuration.Attributes;
namespace ExampleApp;
public sealed class ExampleCsv
{
[Index(0), Name("ProductId")]
public string? Id { get; set; }
[Index(1), Name("Description Short")]
public string? Description { get; set; }
[Index(2, indexEnd: -1)]
public List<int?>? TagIds { get; set; }
public sealed class ExampleCsvMap : ClassMap<ExampleCsv>
{
public ExampleCsvMap()
{
Map(m => m.Id).Index(0).Name("ProductId");
Map(m => m.Description).Index(1).Name("Description Short");
Map(m => m.TagIds).Index(2, -1);
}
}
}
public static class CsvHlpr
{
public static readonly Encoding DEFAULT_ENCODING_FOR_CSV_IMPORTS = Encoding.GetEncoding(1252);
public static CsvReader GetCsvReader(TextReader reader, CsvConfiguration? config = null)
{
config ??= new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ";",
TrimOptions = TrimOptions.Trim,
};
var csvReader = new CsvReader(reader, config);
// this makes empty/whitespace strings map to null
csvReader.Context.TypeConverterOptionsCache
.GetOptions<string>()
.NullValues.AddRange(["", " "]);
return csvReader;
}
}Expected behavior
I would expect that the [Index] attribute at the property works the same as when using it via a ClassMap, but as shown above, [Index(2, indexEnd: -1)] behaves different than Map(m => m.TagIds).Index(2, -1);.