3

I am currently using CSV helper to read contents of a csv file and output it to console. I have installed the csvHelper nuget package. However when I run the code I get the following error:

CsvHelper.TypeConversion.TypeConverterException: 'The conversion cannot be performed. Text: '' MemberType: TypeConverter: 'CsvHelper.TypeConversion.Int32Converter''

I understand that this is due to the fact that the field population in the csv is empty. I would currently like to be able to validate the field and set it to 0. How can I do this with CSVhelper.

My code for reading the csv is:

class ReaderCsv
    {
        private string _cvsfilepath;

        public ReaderCsv(string csvfilepath)
        {
            this._cvsfilepath = csvfilepath;
        }

        public List <Country>  ReadAllCountries()
        {
            var countries = new List<Country>();
            using (var sr = new StreamReader(_cvsfilepath))
            using (var csv = new CsvReader(sr, System.Globalization.CultureInfo.InvariantCulture))
            {


                csv.Configuration.Delimiter = ",";
                csv.Read();
                csv.ReadHeader();

                while (csv.Read()) 
                {
                    var country= new Country();

                    { 
                        country.CountryName = csv.GetField("CountryName");
                        country.CountryCode = csv.GetField("CountryCode");
                        country.Continent = csv.GetField("CountryCode");
                        country.Population = csv.GetField<int>("Population");


                    }
                    countries.Add(country);

                }
            }return countries;


        }

    }
}

my mapping class is


public class CountryMap  : ClassMap<Country>
    {
        public CountryMap()
        {

            Map(m => m.CountryName);
            Map(m => m.CountryCode);
            Map(m => m.Continent);
            Map(m => m.Population);

        }


    }
Eseosa Omoregie
  • 175
  • 2
  • 11
  • https://stackoverflow.com/questions/736629/parse-delimited-csv-in-net It's in the Visual Basic namespace but still works. Bad data is just bad data and you have to decide to either reject it or try to work around it. `Rejection is protection` as a friend of my says frequently. – No Refunds No Returns Mar 19 '20 at 17:31
  • Hi. Can you please mark as answer if my suggestion solves your problem? – Oguz Ozgul Mar 20 '20 at 10:27

2 Answers2

2

The CSV Helper provides overloads of GetField method to which you can pass a custom type converter.

https://joshclose.github.io/CsvHelper/api/CsvHelper/CsvReader/

Therefore; and not only for Int32 but for any type, here is an implementation using a custom generic type converter that returns the default value of the type if the conversion fails.

This does not mean that you have to swallow or ignore the exception. This converter will also give you the conversion error and the offending value so that you can handle this invalid data.

I also added a lineNumber variable to track on which line the invalid data resides.

I hope this helps.

public class Defaulter<T> : CsvHelper.TypeConversion.ITypeConverter
{
    Exception conversionError;
    string offendingValue;

    public Exception GetLastError()
    {
        return conversionError;
    }

    public string GetOffendingValue()
    {
        return offendingValue;
    }

    object CsvHelper.TypeConversion.ITypeConverter.ConvertFromString(string text, IReaderRow row, CsvHelper.Configuration.MemberMapData memberMapData)
    {
        conversionError = null;
        offendingValue = null;
        try
        {
            return (T)Convert.ChangeType(text, typeof(T));
        }
        catch (Exception localConversionError)
        {
            conversionError = localConversionError;
        }
        return default(T);
    }

    string CsvHelper.TypeConversion.ITypeConverter.ConvertToString(object value, IWriterRow row, CsvHelper.Configuration.MemberMapData memberMapData)
    {
        return Convert.ToString(value);
    }
}

And here is the modified version of your code to track the line number as well as to handle the error if you want:

public class ReaderCsv
{
    private string _cvsfilepath;

    public ReaderCsv(string csvfilepath)
    {
        this._cvsfilepath = csvfilepath;
    }

    public List<Country> ReadAllCountries()
    {
        var countries = new List<Country>();
        using (var sr = new StreamReader(_cvsfilepath))
        using (var csv = new CsvReader(sr, System.Globalization.CultureInfo.InvariantCulture))
        {
            csv.Configuration.Delimiter = ",";
            csv.Read();
            csv.ReadHeader();

            Defaulter<int> customInt32Converter = new Defaulter<int>();

            int lineNumber = 0;

            while (csv.Read())
            {
                lineNumber++;
                var country = new Country();
                {
                    country.CountryName = csv.GetField("CountryName");
                    country.CountryCode = csv.GetField("CountryCode");
                    country.Continent = csv.GetField("CountryCode");
                    country.Population = csv.GetField<int>("Population", customInt32Converter);

                    if (customInt32Converter.GetLastError() != null)
                    {
                        // The last conversion has failed.
                        // Handle it here.
                        string errorMessage = "The conversion of Population field on line " + lineNumber + " has failed. The Population value was: [" + customInt32Converter.GetOffendingValue() + "]";

                    }
                }
                countries.Add(country);

            }
        }
        return countries;
    }
}

Regards.

Oguz Ozgul
  • 6,809
  • 1
  • 14
  • 26
0

You could use ClassMap to give a default value for Population

public class Program
{
    public static void Main(string[] args)
    {
        using (MemoryStream stream = new MemoryStream())
        using (StreamWriter writer = new StreamWriter(stream))
        using (StreamReader reader = new StreamReader(stream))
        using (CsvReader csv = new CsvReader(reader, CultureInfo.InvariantCulture))
        {
            writer.WriteLine("CountryName,CountryCode,Continent,Population");
            writer.WriteLine("TestName1,TestCode1,TestContinent1,");
            writer.WriteLine("TestName2,TestCode2,TestContinent2,2");
            writer.Flush();
            stream.Position = 0;

            csv.Configuration.RegisterClassMap<CountryMap>();

            var countries = csv.GetRecords<Country>().ToList();
        }

        Console.ReadKey();
    }
}

public class CountryMap : ClassMap<Country>
{
    public CountryMap()
    {
        Map(m => m.CountryName);
        Map(m => m.CountryCode);
        Map(m => m.Continent);
        Map(m => m.Population).Default(0);
    }
}

public class Country
{
    public string CountryName { get; set; }
    public string CountryCode { get; set; }
    public string Continent { get; set; }
    public int Population { get; set; }
}
David Specht
  • 7,784
  • 1
  • 22
  • 30