Search code examples
c#.netcsvcsvhelper

Read CSV row and map to class with collection of subclass


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.


Solution

  • 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; }
    }