I am reading in a CSV file. There are no headers. I need to map it to a class which has a collection of sub objects. I know the amount of objects in the collection.
Public Class Foo{
public int id {get; set;}
public Bar[] bars {get; set;}
public class Bar{
public int id {get; set;}
public string str {get; set;}
}
}
I am trying to accomplish this using CSVHelper I have tried creating a mapper like below. However I just get the following error: CsvHelper.TypeConversion.TypeConverterException: 'The conversion cannot be performed.
public sealed class Mapper : ClassMap<Foo>
{
public Mapper()
{
Map(m => m.id).Index(0);
Map(m => m.bars).Index(1, 2);
}
}
It seems the Index overload with 2 parameters is expecting to just convert collections of values as opposed to objects constructed from multiple columns.
My actual code has a collection size of 80, with objects with 5 fields on them so bringing them out onto the base Foo object is not ideal.
I know I can pull out the CSV as a string and string split by lines and commas and iterate through them manually but using a proper CSV library seemed cleaner and less prone to oversights.
I see there is also the option to add a References with a map to it
References<BarMap>(m => m.Bars);
public sealed class BarMap : ClassMap<Bar>
{
public BarMap()
{
Map(m => m.id).Index(0);
Map(m => m.str).Index(1);
}
}
But I cannot see how I can appropriately set the Indexes for it. The reference does not allow specifying an index.
CodePudding user response:
I don't think it is possible to automatically map the file to your class, but I've achieved the required result using a DTO class. Considering the data is:
0,0,0,Lorem
1,0,1,Ipsum
2,1,0,Dolor
3,1,1,Sit
4,1,2,Amet
Running the following code
public static void Main(string[] args)
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = false,
};
IEnumerable<FoobarDto> records = null;
using (var reader = new StreamReader("file.csv"))
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<FoobarDtoMap>();
records = csv.GetRecords<FoobarDto>().ToList();
}
var finalRecords = records.GroupBy(x => x.Id).Select(x => new Foo { Id = x.Key, Bars = x.Select(f => f.Bar).ToArray() });
}
public class FoobarDto
{
public int Id { get; set; }
public Foo.Bar Bar { get; set; }
}
public class Foo
{
public int Id { get; set; }
public Bar[] Bars { get; set; }
public class Bar
{
public int Id { get; set; }
public string Str { get; set; }
}
}
public sealed class FoobarDtoMap : ClassMap<FoobarDto>
{
public FoobarDtoMap()
{
Map(m => m.Id).Index(1);
Map(m => m.Bar.Id).Index(2);
Map(m => m.Bar.Str).Index(3);
}
}
It gives you the proper result.
Please note that there should be a unique column at index 0 for CsvHelper
to correctly parse every line of the csv file.
CodePudding user response:
You should be able to use Convert
in your mapping to get the bars
. I assumed two Bar
records, but you can change the for
loop to account for different numbers of Bar
.
void Main()
{
var input = "1,1,Dolor,2,Lorem\n2,3,Sit,4,Ipsum";
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = false,
};
using (var reader = new StringReader(input))
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<Mapper>();
var records = csv.GetRecords<Foo>().ToList();
}
}
public sealed class Mapper : ClassMap<Foo>
{
public Mapper()
{
Map(m => m.id).Index(0);
Map(m => m.bars).Convert(args =>
{
var bars = new List<Bar>();
for (int i = 1; i < 4; i = 2)
{
var bar = new Bar
{
id = args.Row.GetField<int>(i),
str = args.Row.GetField<string>(i 1)
};
bars.Add(bar);
}
return bars.ToArray();
});
}
}
public class Foo
{
public int id { get; set; }
public Bar[] bars { get; set; }
}
public class Bar
{
public int id { get; set; }
public string str { get; set; }
}