Custom conversions when writing CSV files using CsvHelper

15,577

You can do it with a custom type converters (https://github.com/JoshClose/CsvHelper/wiki/Custom-TypeConverter). Just override ConvertToString method.

public class TestConverter : DefaultTypeConverter
{
    public override string ConvertToString(TypeConverterOptions options, object value)
    {
        return base.ConvertToString(options, value);
    }
}

and specify converter when mapping:

Map(m => m.PersonId).Index(0).TypeConverter<TestConverter>();
Share:
15,577
Mike Christensen
Author by

Mike Christensen

Founder and Chief Architect of KitchenPC.com, the world's most powerful recipe search engine. The technology behind KitchenPC is open-source, and available on GitHub.

Updated on July 22, 2022

Comments

  • Mike Christensen
    Mike Christensen almost 2 years

    I've been doing some CSV reading and writing lately, and ran across CsvHelper which is fantastic so far. I've ran into one small problem; I use a custom converter when reading the files, but when I write them back out, that format is lost. My CSV format looks like this:

    67,1234-1,20150115,750,20150115,1340,549,549,406,0,FRG
    

    The fields 20150115,750 map to a single DateTime field called Start (So, 01/15/2015 7:50 AM). My class map looks like this:

    public sealed class DutyMap : CsvClassMap<Duty>
    {
        static readonly CultureInfo enUS = new CultureInfo("en-US");
    
        public DutyMap()
        {
            Map(m => m.PersonId).Index(0);
            Map(m => m.DutyName).Index(1);
            Map(m => m.Start).ConvertUsing(row => ParseDate(row.GetField<String>(2), row.GetField<String>(3)));
            Map(m => m.End).ConvertUsing(row => ParseDate(row.GetField<String>(4), row.GetField<String>(5)));
            Map(m => m.DutyTime1).Index(6);
            Map(m => m.DutyTime2).Index(7);
            Map(m => m.FlightTime).Index(8);
            Map(m => m.CreditHours).Index(9);
            Map(m => m.DutyType).Index(10);
        }
    
        private static DateTime ParseDate(string date, string time)
        {
            DateTime ret;
    
            if (time.Length < 4)
                time = new String('0', 4 - time.Length) + time;
    
            if (!DateTime.TryParseExact(date + time, "yyyyMMddHHmm", enUS, DateTimeStyles.None, out ret))
                throw new FormatException(String.Format("Could not parse DateTime.  Date: {0} Time: {1}", date, time));
    
            return ret;
        }
    }
    

    This works great, and I can now call parse an entire file like so:

    var csv = new CsvReader(sr);
    csv.Configuration.HasHeaderRecord = false;
    csv.Configuration.RegisterClassMap<DutyMap>();
    
    Data = csv.GetRecords<Duty>().ToList();
    

    However, when I write the file:

    csv.WriteRecords(Data);
    

    The file is written out like so:

    67,7454-1,1/15/2015 7:50:00 AM,1/15/2015 1:40:00 PM,549,549,406,0,FPG
    

    Looking through the documentation, I don't see a way to specify a conversation function upon writing, only reading. The only solution I've found so far is to manually write each record by hand:

    var csv = new CsvWriter(sw);
    foreach (var item in Data)
    {
        csv.WriteField(item.PersonId);
        csv.WriteField(item.DutyName);
        csv.WriteField(item.Start.ToString("yyyyMMdd"));
        csv.WriteField(item.Start.ToString("Hmm"));
        csv.WriteField(item.End.ToString("yyyyMMdd"));
        csv.WriteField(item.End.ToString("Hmm"));
        csv.WriteField(item.DutyTime1);
        csv.WriteField(item.DutyTime2);
        csv.WriteField(item.FlightTime);
        csv.WriteField(item.CreditHours);
        csv.WriteField(item.DutyType);
    
        csv.NextRecord();
    }
    

    Is there a better way to do this? Thanks!

  • Mike Christensen
    Mike Christensen over 8 years
    Thanks! I started looking at that, but the documentation looked a bit sparse. I'll dig into it more!
  • Mike Christensen
    Mike Christensen over 8 years
    Oh, also would this let me split one field (Start) into two different fields in the CSV file? Remember, I have to split up the date portion and time portion.
  • Daniel Luberda
    Daniel Luberda over 8 years
    Just use Map statements multiple times with different columns names Map(m => m.PersonId).Index(0).Name("col1"); Map(m => m.PersonId).Index(0).Name("col2");
  • Vasily Hall
    Vasily Hall over 6 years
    I had to use this code: public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)