CsvHelper: Data Validation exceptions do not provide adequate information about exception to be helpful

Viewed 1291

When considering 'data' validation upon import of csv data using CsvHelper, I was hoping to put all of my 'data' validation code in the ClassMap using the .Validate method.

I am have been looking for good examples of how to use the .Validate method to ensure that the data conforms to my business rules and also send good error message back to the user when validation rules have been violated.

A very basic example is located in the 'Configuration/Class Maps/Validation code located in 'examples' section of the CsvHelper website located here: https://joshclose.github.io/CsvHelper/examples/configuration/class-maps/validation/

I am able to predictably able to make and catch errors that I have set up using the .Validate method. However, I have not determined a way to give adequate information to my user to fix issues when they are encountered. It appears to me that the FieldValidationException does not contain enough information to be useful. I have seen others asking this question but the other posts that I have seen do not have an adequate answer to the problem. Please see my code and comments below.

 class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //  load comma separated data into string for processing
                var s = new StringBuilder();
                s.AppendLine("Id,Name");
                s.AppendLine("1,one-e");

                //  Begin processing
                using (var reader = new StringReader(s.ToString()))
                using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
                {
                    csv.Context.RegisterClassMap<FooMap>();
                    var contents = csv.GetRecords<Foo>().ToList();
                    Console.WriteLine($"Read file: length is : {contents.First().Id},{contents.First().Name},{contents.First().Date}");
                }
            }
            catch (FieldValidationException fieldExc)
            {
                //  this exception object does not appear to hold any information about the column that is in error. It does not seem to specify what the error entails.
                //  It appears to contain the value that created the exception?  In a 'string' field called: "Field". 
                //  I would really like further information about what is specifically is wrong with which column and possibly which row.
                Console.WriteLine($"Error Message is : {fieldExc.Message}");
            }
            catch(TypeConverterException converterExc)
            {
                //  If I happen to have a conversion exception, then the resulting TypeConversionException has good information to advise 
                //  a user to correct an error.
                var message = FriendlyErrorText(converterExc);
                Console.WriteLine(message);
            }
            catch (Exception exc)
            {
                Console.WriteLine($"Error Message is : {exc.Message}");
            }
            finally
            {
                Console.WriteLine("test executed");
                Console.ReadKey();
            }
        }

        public class Foo
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public DateTimeOffset? Date { get; set; }
        }

        public class FooMap : ClassMap<Foo>
        {
            public FooMap()
            {
                Map(m => m.Id);
                Map(m => m.Name).Validate(field => !field.Field.Contains("-"));
            }
        }

        public static string FriendlyErrorText(TypeConverterException exception)
        {
            var column = exception?.MemberMapData?.Member?.Name ?? "Unknown";
            string typeConversion;
            switch (exception?.TypeConverter?.ToString())
            {
                case "Int32Converter":
                    typeConversion = "integer";
                    break;
                default:
                    typeConversion = exception?.TypeConverter?.GetType()?.Name ?? "Unknown";
                    break;
            }
            var message = $"There was an error importing the text data '{exception?.Text ?? "Unknown"}' into the column {column}. The target column is of type {typeConversion}";
            return message;
        }
    }


1 Answers

FieldValidationException includes the CsvContext, which has most of what you would need.

catch (FieldValidationException fieldExc)
{
    var message = string.Empty;
    var headerIndex = fieldExc.Context.Reader.CurrentIndex;
    if(fieldExc.Context.Reader.HeaderRecord[headerIndex].ToLower() == "name")
    {
        message = "Name cannot contain a dash.";
    }
    
    Console.WriteLine($"Error Message is : Field Validation Exception on Row: {fieldExc.Context.Parser.Row}, Message: {message} Original Text: \"{fieldExc.Field}\"");
}
Related