Search code examples
c#csvcsvhelper

CsvHelper - Read different record types in same CSV


I'm trying to read two types of records out of a CSV file with the following structure:

PlaceName,Longitude,Latitude,Elevation
NameString,123.456,56.78,40

Date,Count
1/1/2012,1
2/1/2012,3
3/1/2012,10
4/2/2012,6

I know this question has been covered previously in

but when I run my implementation it gets a CsvMissingFieldException saying that Fields 'Date' do not exist in the CSV file. I have two definition and map classes, one for the location and the other for the counts, which are:

public class LocationDefinition
{
    public string PlaceName { get; set; }
    public double Longitude { get; set; }
    public double Latitude { get; set; }
    public double Elevation { get; set; }
}

public sealed class LocationMap : CsvClassMap<LocationDefinition>
{
    public LocationMap()
    {
        Map(m => m.PlaceName).Name("PlaceName");
        Map(m => m.Longitude).Name("Longitude");
        Map(m => m.Latitude).Name("Latitude");
        Map(m => m.Elevation).Name("Elevation");
    }            
}     

public class CountDefinition
{
    public DateTime Date { get; set; }
    public int Count { get; set; }
}

public sealed class CountMap : CsvClassMap<CountDefinition>
{
    public CountMap()
    {
        Map(m => m.Date).Name("Date");
        Map(m => m.Count).Name("Count");
    }
}

The code that I have for reading the csv file is:

LocationDefinition Location;
var Counts = new List<CountDefinition>();

using (TextReader fileReader = File.OpenText(@"Path\To\CsvFile"))
using (var csvReader = new CsvReader(fileReader))
{
    csvReader.Configuration.RegisterClassMap<LocationMap>();
    csvReader.Configuration.RegisterClassMap<CountMap>();

    // Only reads a single line of Location data
    csvReader.Read();
    LocationData = csvReader.GetRecord<LocationDefinition>();
    csvReader.Read(); // skip blank line
    csvReader.Read(); // skip second header section

    // Read count data records
    while (csvReader.Read())
    {
        var tempCount = csvReader.GetRecord<CountDefinition>();
        Counts.Add(tempCount);
    }
}

The exception gets thrown on the tempCount line. From what I can tell it still expects a Location record, but I would have thought GetRecord<CountDefinition> would specify the record type. I've also tried ClearRecordCache and unregistering the LocationMap to no avail.

How should this code be changed to get it to read a csv file of this structure?


Solution

  • I got a response from Josh Close on the issue tracker:

    CsvReader not recognising different registered class maps

    Here is his answer to this question:

    Since you don't have a single header, you'll need to ignore headers and use indexes instead. This brings up an idea though. I could have the ReadHeader method parse headers for a specific record type.

    Here is an example that should work for you though.

    void Main()
    {
        LocationDefinition Location;
        var Counts = new List<CountDefinition>();
    
        using (var stream = new MemoryStream())
        using (var reader = new StreamReader(stream))
        using (var writer = new StreamWriter(stream))
        using (var csvReader = new CsvReader(reader))
        {
            writer.WriteLine("PlaceName,Longitude,Latitude,Elevation");
            writer.WriteLine("NameString,123.456,56.78,40");
            writer.WriteLine();
            writer.WriteLine("Date,Count");
            writer.WriteLine("1/1/2012,1");
            writer.WriteLine("2/1/2012,3");
            writer.WriteLine("3/1/2012,10");
            writer.WriteLine("4/2/2012,6");
            writer.Flush();
            stream.Position = 0;
    
            csvReader.Configuration.HasHeaderRecord = false;
            csvReader.Configuration.RegisterClassMap<LocationMap>();
            csvReader.Configuration.RegisterClassMap<CountMap>();
    
            csvReader.Read(); // get header
            csvReader.Read(); // get first record
            var locationData = csvReader.GetRecord<LocationDefinition>();
    
            csvReader.Read(); // skip blank line
            csvReader.Read(); // skip second header section
    
            // Read count data records
            while (csvReader.Read())
            {
                var tempCount = csvReader.GetRecord<CountDefinition>();         
                Counts.Add(tempCount);
            }
        }
    }
    
    public class LocationDefinition
    {
        public string PlaceName { get; set; }
        public double Longitude { get; set; }
        public double Latitude { get; set; }
        public double Elevation { get; set; }
    }
    
    public sealed class LocationMap : CsvClassMap<LocationDefinition>
    {
        public LocationMap()
        {
            Map(m => m.PlaceName);
            Map(m => m.Longitude);
            Map(m => m.Latitude);
            Map(m => m.Elevation);
        }
    }
    
    public class CountDefinition
    {
        public DateTime Date { get; set; }
        public int Count { get; set; }
    }
    
    public sealed class CountMap : CsvClassMap<CountDefinition>
    {
        public CountMap()
        {
            Map(m => m.Date);
            Map(m => m.Count);
        }
    }