Write a CSV file with a dictionary field as separate column headers
Is it possible to create a CSV file which has a dictionary collection, where each key in the dictionary is mapped as separate column headers. This is the best I've come up with so far:
public class ProductViewModel {
public int Id { get; set; }
public string Name { get; set; }
public IDictionary<string, object?> Attributes { get; set; }
}
public class ProductViewModelMap : ClassMap<ProductViewModel> {
public ProductViewModelMap() {
Map(p => p.Id);
Map(p => p.Name);
Map(m => m.Attributes).Name(new[] { "Attribute1", "Attribute2" });
}
}
var products = new List<ProductViewModel> {
new ProductViewModel { Id = 1, Name = "Product 1", Attributes = new Dictionary<string, object?> { { "Attribute1", 10 }, { "Attribute2", "Value" } } },
new ProductViewModel { Id = 2, Name = "Product 2", Attributes = new Dictionary<string, object?> { { "Attribute1", 20 }, { "Attribute2", "Value" } } }
};
using (var writer = new StreamWriter("file.csv"))
using (var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture))) {
csv.Context.RegisterClassMap<ProductViewModelMap>();
await csv.WriteRecordsAsync(products);
}
However this produces:
Id,Name,Attribute1
1,Product 1,10,Value
2,Product 2,20,Value
But I would like:
Id,Name,Attribute1,Attribute2
1,Product 1,10,Value
2,Product 2,20,Value
I've tried numerous things but nothing seems to work. Ideally I'd like to use a ClassMap instead of writing the raw contents as I think this should be supported out of the box.
why you've closed it? have you found the solution to get expected result Id,Name,Attribute1,Attribute2?
I have the same problem now.
Hi @b-maslennikov,
I'm not sure why I closed this out. For now I got around this by doing the following:
using (var writer = new StreamWriter("file.csv"))
using (var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture) {
HasHeaderRecord = false
})) {
csv.Context.RegisterClassMap<ProductViewModelMap>();
csv.WriteHeader<ProductViewModel>(["Attribute1", "Attribute2"]);
await csv.WriteRecordsAsync(products);
}```
Here's my extension method to manually write the header:
```cs
public static class CsvWriterExtensions {
public static void WriteHeader<T>(this CsvWriter csv, IEnumerable<string> attributeNames) {
foreach (var property in typeof(T).GetProperties().Where(p => p.Name != "Attributes")) {
csv.WriteField(property.Name);
}
foreach (var attributeName in attributeNames) {
csv.WriteField(attributeName);
}
csv.NextRecord();
}
}
My temporary solution: replace classmap with method that converts T to dynamic. + WriteDynamicHeader() It would great to have requested functionality...