
Photo by Mika Baumeister
When working with C# and CSV files, I always use CSVHelper. It's a small library for reading and writing CSV files. Extremely fast, flexible, and easy to use. I couldn't agree more, it's simply awesome :).
Now, I ran into a rather delicate problem a couple of days ago.
Say that you a CSV file looking like this:
id,categories
1234,"food,pasta,meat"
4321,"food,beer,alcohol"
You then have a class that you want to map the data to:
public class GoodFoodCsvRow
{
public string Id { get; set; }
public List<string> Categories { get; set; }
}
And your CSVHelper Map looks like this:
public class GoodFoodCsvRowMap : ClassMap<GoodFoodCsvRow>
{
public GoodFoodCsvRowMap()
{
Map(x => x.Id).Index(0);
Map(x => x.Categories).Index(1);
}
}
If you now were to read said CSV file like this...
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
using (var reader = new StreamReader(responseStream))
{
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
csv.Configuration.HasHeaderRecord = true;
csv.Configuration.RegisterClassMap<GoodFoodCsvRowMap>();
var rows = csv.GetRecords<GoodFoodCsvRow>();
return rows.ToDictionary(x => x.Id, x => x.Categories);
}
}
}
}
...you would notice that the Categories
list only contains one string looking like this:
That's not what we want, we want each category to be a separate item in the Categories
list.
Luckily, it's really easy to achieve using CSVHelper, we just need to update our GoodFoodCsvRowMap
so it looks like this:
public class GoodFoodCsvRowMap : ClassMap<GoodFoodCsvRow>
{
public GoodFoodCsvRowMap()
{
Map(x => x.Id).Index(0);
Map(x => x.Categories).Index(1).ConvertUsing(row =>
{
var columnValue = row.GetField<string>("categories");
return columnValue?.Split(',')?.ToList() ?? new List<string>();
});
}
}
Now when we run it again the problem will be solved:
.