Search code examples
csvhelper

CsvHelper Format Decimal using ClassMap


I have column columns in the file that will have values like "000120000" that needs to be converted into "1200.00" using CsvHelper ClassMap. I am currently using Decimal type for those columns and have the NumberStyle in the ClassMap that is truncating the zero's at the beginning. So i am getting the value as "120000". I tried using the "TypeConverterOption.Format("0.00")" but no luck. So would like to know if there is a way i can convert the value "120000" to "1200.00" using ClassMap. I can even divide by 100 but don't want to do this everywhere i use the field so need to do this logic in the ClassMap. Thank you in advance for helping me out on this.

In the ClassMap I have the below code: csvConfig.TypeConverterOptionsCache.GetOptions<decimal?>().NumberStyle = NumberStyles.Number | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent;

Ex: "000120000" -> "1200.00" (right now i am getting the value as "120000")


Solution

  • This is one way you could do it. With a custom converter.

    public class Program
    {
        static void Main(string[] args)
        {
            using (var stream = new MemoryStream())
            using (var writer = new StreamWriter(stream))
            using (var reader = new StreamReader(stream))
            using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
            {
                writer.WriteLine("Id,MoveDecimalPoint,NoMoveDecimalPoint");
                writer.WriteLine("1,000120000,000120000");
                writer.Flush();
                stream.Position = 0;
    
                csv.Configuration.RegisterClassMap<FooClassMap>();
    
                var records = csv.GetRecords<Foo>().ToList();
            }
        }
    }
    
    public class Foo
    {
        public int Id { get; set; }
        public decimal MoveDecimalPoint { get; set; }
        public decimal NoMoveDecimalPoint { get; set; }
    }
    
    public class FooClassMap : ClassMap<Foo>
    {
        public FooClassMap()
        {
            AutoMap(CultureInfo.InvariantCulture);
            Map(m => m.MoveDecimalPoint).TypeConverter<MoveDecimalConverter>();
        }
    }
    
    public class MoveDecimalConverter : DecimalConverter
    {
        public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
        {
            var numberStyle = memberMapData.TypeConverterOptions.NumberStyle ?? NumberStyles.Number;
    
            if (decimal.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var d))
            {
                return d / 100;
            }
    
            return base.ConvertFromString(text, row, memberMapData);
        }
    }
    

    If you wanted to have ALL decimal values move two decimal places, you could add the MoveDecimalConverter to the configuration.

    public class Program
    {
        static void Main(string[] args)
        {
            using (var stream = new MemoryStream())
            using (var writer = new StreamWriter(stream))
            using (var reader = new StreamReader(stream))
            using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
            {
                writer.WriteLine("Id,MoveDecimalPoint,NoMoveDecimalPoint");
                writer.WriteLine("1,000120000,000120000");
                writer.Flush();
                stream.Position = 0;
    
                csv.Configuration.TypeConverterCache.AddConverter<decimal>(new MoveDecimalConverter());
    
                var records = csv.GetRecords<Foo>().ToList();
            }
        }
    }
    

    You could also use ConvertUsing()

    public class Program
    {
        static void Main(string[] args)
        {
            using (var stream = new MemoryStream())
            using (var writer = new StreamWriter(stream))
            using (var reader = new StreamReader(stream))
            using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
            {
                writer.WriteLine("Id,MoveDecimalPoint,NoMoveDecimalPoint");
                writer.WriteLine("1,000120000,000120000");
                writer.Flush();
                stream.Position = 0;
    
                csv.Configuration.RegisterClassMap<FooClassMap>();
    
                var records = csv.GetRecords<Foo>().ToList();
            }
        }
    }
    
    public class Foo
    {
        public int Id { get; set; }
        public decimal MoveDecimalPoint { get; set; }
        public decimal NoMoveDecimalPoint { get; set; }
    }
    
    public class FooClassMap : ClassMap<Foo>
    {
        public FooClassMap()
        {
            AutoMap(CultureInfo.InvariantCulture);
            Map(m => m.MoveDecimalPoint).ConvertUsing(row =>
            {
                if (decimal.TryParse(row["MoveDecimalPoint"], NumberStyles.Number, row.Configuration.CultureInfo, out var d))
                {
                    return d / 100;
                } 
                else
                {
                    return 0;
                }
            });
        }
    }