Search code examples
c#csvhelper

CsvHelper, list the expected header when headers are invalid


Reading a CSv file with CsvHelper you can specify the expected Header in the ClassMap:

public sealed class readFooMapper : ClassMap<FooCSV>
{
    public readFooMapper()
    {
        Map(m => m.Id).ConvertUsing(row => ((CsvReader)row).Parser.Context.RawRow);
        Map(x => x.Foo).Name("Foo");
        Map(x => x.Bar).Name("Bar");
        Map(x => x.FooBar).Name("FooBar");
        Map(x => x.MyOptional).Name("MyOptional").Optional();
    }

}

Every property mapped is required and expected, optional is use to distiguish property that migh or might not be present.

Header are validate by Action<bool, string[], int, ReadingContext> HeaderValidated, with the respective Arguments: isValid, headerNames, headerNameIndex, context. When a header is not missing isValid is false. headerNames and 'headerNameIndex' will respectivlly contains the expect names or index for the header. I can use it to know with header is missing.

How can I access the list of expected header? Many properties of ReadingContext context will hold the list of current header.

I can use the Validation loop to Add all the headerNames and 'headerNameIndex' to a list.

var errorSb = new StringBuilder();
[...]
csvReader.Configuration.HeaderValidated =
    (isValid, headerNames, headerNameIndex, context) =>
    {
        allHeaderNames.Add(headerNames);
        if (!isRowValid)
        {
            isHeaderInvalid= true;
        }
    };

What I really want is the readFooMapper mapping configuration.
To tell the user: "Look I expect This list of columns: ##, ##, ##. With those optional columns: X,Y,Z. \n Missing column = A,B,C."

And this without having to maintain one an other lists Headers and Optional.


Solution

  • I think this gets close to what you are looking for. It won't check if there is a column referenced in your ConvertUsing method.

    var map = new readFooMapper();
    
    var required = map.MemberMaps
                      .Where(m => m.Data.Ignore == false 
                               && m.Data.ReadingConvertExpression == null 
                               && m.Data.IsOptional == false)
                      .Select(m => m.Data.Member.Name)
                      .ToList();
    var optional = map.MemberMaps
                      .Where(m => m.Data.Ignore == false 
                               && m.Data.ReadingConvertExpression == null 
                               && m.Data.IsOptional == true)
                      .Select(m => m.Data.Member.Name)
                      .ToList();