csvhelper

With CsvHelper, how do I make it use string.Empty instead of Null for missing varchar fields?


Say I have a c# class "Person" with a property named "FirstName". In the database, FirstName is a VARCHAR(100) NOT NULL field. When CsvHelper imports a CSV that lacks a FirstName field, I need it to use a default value of string.Empty for that field rather than Null. I don't want to create a custom Map for each record type because I have many of those, so how can I configure CsvHelper to automatically use string.Empty instead of Null when it encounters a missing varchar field on any record type it encounters?

Here is some of the code I'm using:

List<T> recordBatch = csvReader.GetRecords<T>().Take(100).ToList();

// now, recordBatch contains a 100 instances of a class 
// that has null for FirstName

I tried using the MissingFieldFound callback, but can't find a way within that to detect the datatype of the missing field using the MissingFieldFoundArgs, nor how to set the missing value to string.Empty.

I tried configuring a simple StringEmptyConverter class that just returns string.Empty, but that didn't have any effect:

csvReader.Context.TypeConverterCache.AddConverter<string>(new StringEmptyConverter());

public class StringEmptyConverter : DefaultTypeConverter
{
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        return text ?? string.Empty;  // If the field is missing or null, return string.Empty
    }
}

Solution

  • If the CSV file is missing FirstName in the header, unfortunately, CsvHelper will pretty much ignore that property when creating the record. As you have seen, the FirstName property will not hit your custom StringEmptyConverter, even though any other string property that is in the header will.

    Would an extension method that maps all string properties to have a default of string.Empty work?

    void Main()
    {
        var data = "Id,LastName\n1,Smith\n2,Jones";
        
        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            MissingFieldFound = null,
            HeaderValidated = null,
        };
    
        var reader = new StringReader(data);
        var csv = new CsvReader(reader, config);
        
        csv.MapStringsToEmptyDefault<Person>();
        
        var records = csv.GetRecords<Person>();
    
        records.Dump();
    }
    
    public static class MyExtensions
    {
        public static void MapStringsToEmptyDefault<T>(this CsvReader csv, ClassMap<T> classMap = null)
        {
            if (classMap == null)
            {
                classMap = new DefaultClassMap<T>();
                classMap.AutoMap(CultureInfo.InvariantCulture);
            }
            
            var properties = typeof(T).GetProperties().Where(x => x.PropertyType.Name == "String").ToArray();
            foreach (PropertyInfo property in properties)
            {
                classMap.Map(typeof(T), property).Default(string.Empty);
            }
            csv.Context.RegisterClassMap(classMap);
        }
    }
    
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }