2

Running CSVHelper 7.0.0 and trying to add a custom string convertor that can be applied to specific class map fields (do not want to applied globally to all fields of type string). Below are snippets on how I currently have my class map, custom convertor, and csv writter calls setup.

Class Map code snippet with custom convertor on NextReviewDate map field:

public sealed class MyCustomClassMap : ClassMap<MyCustomClass>
{
    public MyCustomClassMap()
    {
        Map(m => m.ContentId).Index(0);
        Map(m => m.Name).Index(1);
        Map(m => m.ContentOwner).Index(2);
        Map(m => m.ContentOwnerName).Index(3);
        Map(m => m.CopyrightOwner).Index(4);
        Map(m => m.CopyrightOwnerName).Index(5);
        Map(m => m.NextReviewDate).Index(6).TypeConverter<DateTimeStringConverter>();
        Map(m => m.ContentStatus).Index(7);
        Map(m => m.UsageRights).Index(8);
        Map(m => m.SchemaName).Index(9);
    }
}

Custom string converter code snippet:

public class DateTimeStringConverter : StringConverter
{
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        string formattedDateString = string.Empty;

        if (DateTime.TryParse(text, out DateTime dateobj))
        {
            formattedDateString = dateobj.ToString("MM-dd-yyyy");
        }

        //throw new Exception("DateTimeStringConverter value: " + formattedDateString);

        return formattedDateString;
    }
}   

Snippet of code of how I am registering my class map and write records:

csv.Configuration.RegisterClassMap<MyCustomClassMap>();

csv.WriteRecords(results);

To troubleshoot I added a throw exception in DateTimeStringConverter and appears it never gets called. Am I missing a piece? Right now the CSV is generating and includes the original NextReviewDate map field value without ever calling the custom convertor.

EDIT: based on @Self feedback changing custom string converter to the following resolved issue:

public class DateTimeStringConverter : DefaultTypeConverter
{
    public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
    {
        string strVal = (string)value;

        if (DateTime.TryParse(strVal, out DateTime dateobj))
        {
            strVal = dateobj.ToString("MM-dd-yyyy");
        }

        return strVal;
    }
}
seanrco
  • 935
  • 5
  • 16
  • 32
  • 1
    convertFrom string is for reading , and ConvertToString should be for writing. – Self Apr 14 '21 at 08:08
  • If you have issue with converter you can also simply ignore one property and add one with getter that will return in the right format. – Self Apr 14 '21 at 08:16
  • @Self Switching to ConvertToString override resolved the issue, thanks! – seanrco Apr 14 '21 at 10:02

1 Answers1

3

CSV Helper 26.1.0

First StringConverter offers only one method to overwrite object ConvertFromString(..).
The converstion to string is handled by nothing because it's suppose to be a string.

Here I supposse that your Type is DateTime and you got it in multiple Exotique format. If you have only one format you can change the default format for that type.

A simple demo class and it's mapping:

public class Test
{
    public int Id { get; set; }
    public DateTime DateTime { get; set; }
    public DateTime Date { get; set; }
    public DateTime Time { get; set; }
}
public sealed class TestMap : ClassMap<Test>
{
    public TestMap()
    {
        AutoMap(CultureInfo.InvariantCulture);
        Map(x => x.Date).TypeConverter(new DateStringConverter("MM - dd - yyyy"));
        Map(x => x.Time).TypeConverter(new DateStringConverter("mm # hh # ss"));
    }
}

I used a converter that inherit from ITypeConverter in order to have both ConvertFromString and ConvertToString.
With Customisable Format, culture, and style.

public class DateStringConverter : ITypeConverter
{
    private readonly string _dateFormat;
    private readonly CultureInfo _CultureInfo;
    private readonly DateTimeStyles _DateTimeStyles;

    public DateStringConverter(string dateFormat) :
    this(dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None)
    { }

    public DateStringConverter(string dateFormat, CultureInfo cultureInfo, DateTimeStyles dateTimeStyles)
    {
        _dateFormat = dateFormat;
        _CultureInfo = cultureInfo;
        _DateTimeStyles = dateTimeStyles;
    }

    public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        string formattedDateString = string.Empty;
        if (DateTime.TryParseExact(text, _dateFormat, _CultureInfo, _DateTimeStyles, out DateTime dateObj))
        {
            return dateObj;
        }
        return null;
    }
    public string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
    {
        if (value == null) return string.Empty;

        if (DateTime.TryParse(value.ToString(), out DateTime dt))
            return dt.ToString(_dateFormat);
        else
            return string.Empty;
    }
}

Writing a CSV:

using (var writer = new StringWriter())
using (var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture, true))
{
    csvWriter.Context.RegisterClassMap<TestMap>();
    csvWriter.WriteRecords(datas);
    csvWriter.Flush();
    csvTextOuput = writer.ToString();
}

Result:

Id,DateTime,Date,Time
1,04/14/2021 09:18:02,04 - 14 - 2021,18 # 09 # 02
2,04/15/2021 09:18:02,04 - 15 - 2021,18 # 09 # 02
3,04/16/2021 12:18:02,04 - 16 - 2021,18 # 12 # 02

Reading a CSV:

using (var reader = new StringReader(csvTextOuput))
using (var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture, true))
{
    csvReader.Context.RegisterClassMap<TestMap>();
    ObjectFromCSV = csvReader.GetRecords<Test>().ToArray();
}

Result:

[
   {
   Date      : 04/14/2021
   DateTime  : 04/14/2021
   Id        : 1
   Time      : 04/14/2021
   },
   {
   Date      : 04/15/2021
   DateTime  : 04/15/2021
   Id        : 2
   Time      : 04/14/2021
   },
   {
   Date      : 04/16/2021
   DateTime  : 04/16/2021
   Id        : 3
   Time      : 04/14/2021
   }
]

Live demo https://dotnetfiddle.net/EMdhtn


CSV Helper 7

https://dotnetfiddle.net/5DgwxY

The only modification should be the absence of culture in the reader/writer ctor. And RegisterClassMap that moved from Configuration to Context

  • ~new CsvReader(reader, CultureInfo.InvariantCulture, true))~ => new CsvReader(reader))
  • ~csvWriter.Context.RegisterClassMap()~ => csvWriter.Configuration.RegisterClassMap();

Homogenous datetime format accros all property.

In case youhave the same format everywhere you those proposed solution:

N.B:TypeConverterFactory or TypeConverterCache on older version.

Self
  • 349
  • 1
  • 8
  • 1
    While my issue was resolved by updating my override to ConvertToString (see main post comments and post edit updates I am marking this as most relevant answer since it is so in depth and more pertinent to recent version of CSVHelper. Thanks for the all the excellent feedback @Self – seanrco Apr 14 '21 at 10:18