Add support for IDictionary<string, object?>
Say I have the following model and mapping:
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; } = default!;
public IDictionary<string, object?> Attributes { get; set; } = default!;
}
public class ProductViewModelMap : ClassMap<ProductViewModel>
{
public ProductViewModelMap()
{
AutoMap(CultureInfo.InvariantCulture);
Map(m => m.Attributes).Index(2, 3);
}
}
Now if I say:
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 2" } } }
};
using (var writer = new StreamWriter("file.csv"))
using (var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = false
}))
{
writer.WriteLine("Id,Name,Attribute1,Attribute2");
await csv.WriteRecordsAsync(products);
}
using (var reader = new StreamReader("file.csv"))
using (var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture)))
{
csv.Context.RegisterClassMap<ProductViewModelMap>();
while (await csv.ReadAsync())
{
var record = csv.GetRecord<ProductViewModel>()!;
}
}
I receive the error:
CsvHelper.TypeConversion.TypeConverterException: 'The conversion cannot be performed. Text: '10' MemberName: Attributes MemberType: System.Collections.Generic.IDictionary`2[[System.String, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] TypeConverter: 'CsvHelper.TypeConversion.IDictionaryGenericConverter' IReader state: ColumnCount: 0 CurrentIndex: 2 HeaderRecord: ["Id","Name","Attribute1","Attribute2"] IParser state: ByteCount: 0 CharCount: 53 Row: 2 RawRow: 2 Count: 4 RawRecord: 1,Product 1,10,Value'
I added the following dictionary converter to fix my issue:
public class MyIDictionaryGenericConverter : IDictionaryConverter
{
public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData)
{
var dictionary = new Dictionary<string, object?>();
var indexEnd = memberMapData.IndexEnd < memberMapData.Index
? row.Parser.Count - 1
: memberMapData.IndexEnd;
for (var i = memberMapData.Index; i <= indexEnd; i++)
{
dictionary.Add(row.HeaderRecord[i], row.GetField(i));
}
return dictionary;
}
}
But it would be nice if this was supported out of the box. Also it would be much nicer if there was a better way of adding a header for all the keys in the dictionary. The only way I have managed to achieve it, is to write it manually (as shown above).
Just a follow up that I had to modify my converter to feed in the value types. For example:
public class MyIDictionaryGenericConverter : IDictionaryConverter
{
private readonly Type[] _valueTypes;
public MyIDictionaryGenericConverter(Type[] valueTypes)
{
_valueTypes = valueTypes;
}
public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData)
{
var dictionary = new Dictionary<string, object?>();
var indexEnd = memberMapData.IndexEnd < memberMapData.Index
? row.Parser.Count - 1
: memberMapData.IndexEnd;
var counter = 0;
for (var i = memberMapData.Index; i <= indexEnd; i++)
{
var value = row.GetField(i);
var converter = row.Context.TypeConverterCache.GetConverter(_valueTypes[counter]);
var field = !string.IsNullOrEmpty(value) ? converter.ConvertFromString(value, row, memberMapData) : null;
dictionary.Add(row.HeaderRecord[i], field);
counter++;
}
return dictionary;
}
}