Search code examples
c#csvcsvhelper

How to configure CsvHelper to skip MissingFieldFound rows


public interface ICsvProductReaderConfigurationFactory
{
    Configuration Build();
}

public class CsvProductReaderConfigurationFactory : ICsvProductReaderConfigurationFactory
{
    private readonly ClassMap<ProductDto> classMap;

    public CsvProductReaderConfigurationFactory(IProductDtoClassMapProvider classMapProvider)
    {
        classMap = classMapProvider.Get();
    }

    public Configuration Build()
    {
        var config = new Configuration
        {
            Delimiter = "\t",
            HasHeaderRecord = true,
            IgnoreQuotes = true,
            MissingFieldFound = (rows, fieldIndex, readingContext) =>
                Log.Warn($"Missing Field Found at line {readingContext.Row}\r\n" +
                         $"Field at index {fieldIndex} does not exist\r\n" +
                         $"Raw record: {readingContext.RawRecord}"),
            BadDataFound = context => 
                Log.Warn($"Bad data found at row {context.Row}\r\n" +
                         $"Raw data: {context.RawRecord}")
        };

        config.RegisterClassMap(classMap);
        return config;
    }
}


public interface ICvsProductReader
{
    IEnumerable<ProductDto> GetAll(string filePath);
}

public class CvsProductReader : ICvsProductReader
{
    private readonly ICsvProductReaderConfigurationFactory csvProductReaderConfigurationFactory;

    public CvsProductReader(ICsvProductReaderConfigurationFactory csvProductReaderConfigurationFactory)
    {
        this.csvProductReaderConfigurationFactory = csvProductReaderConfigurationFactory;
    }

    public IEnumerable<ProductDto> GetAll(string filePath)
    {
        var csvReaderConfiguration = csvProductReaderConfigurationFactory.Build();

        using (var streamReader = new StreamReader(filePath))
        using (var csvReader = new CsvReader(streamReader, csvReaderConfiguration))
        {
            return csvReader.GetRecords<ProductDto>().ToArray();
        }
    }
}

MissingFieldFound property is called when a missing field is found, but can not affect result.

I was wondering if it's possible to configure CsvHelper to skip rows with missing fields.


Solution

  • There is nothing wrong the way you did it, here is a mcve showing a complete exemple

    var good = new List<Test>();
    var bad = new List<string>();
    
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream))
    using (var reader = new StreamReader(stream))
    using (var csv = new CsvReader(reader))
    {
        writer.WriteLine("FirstName,LastName");
        writer.WriteLine("\"Jon\"hn\"\",\"Doe\"");
        writer.WriteLine("\"JaneDoe\"");
        writer.WriteLine("\"Jane\",\"Doe\"");
        writer.Flush();
        stream.Position = 0;
    
        var isRecordBad = false;
    
        csv.Configuration.BadDataFound = context =>
        {
            isRecordBad = true;
            bad.Add(context.RawRecord);
        };
    
        csv.Configuration.MissingFieldFound = (headerNames, index, context) =>
        {
            isRecordBad = true;
            bad.Add(context.RawRecord);
        };
    
        while (csv.Read())
        {
            var record = csv.GetRecord<Test>();
            if (!isRecordBad)
            {
                good.Add(record);
            }
    
            isRecordBad = false;
        }
    }
    
    good.Dump();
    bad.Dump();