JoshClose / CsvHelper

Library to help reading and writing CSV files

Home Page:http://joshclose.github.io/CsvHelper/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using Custom TypeConverter causes dangerous/spurious CsvWriter behavior

jzabroski opened this issue · comments

Describe the bug
On CsvHelper 31.0.4:

When using a custom TypeConverter:

  1. data can be completely omitted, causing columns to shift left one or more columns
  2. data can be written out-of-order from the column headers

To Reproduce
This is not a 100% complete repro yet, but illustrative of the problem.
SodPosition is a gRPC object that I cannot concisely copy-paste. I plan to work on whittling this down to a simpler repro.
I've mangled the names a bit to abstract away what problem domain this is for.

    public class RejectedAmendment
    {
        public SodPosition SodPosition { get; set; }
        public string ValidationMessage { get; set; }

        public override string ToString()
        {
            return $"ValidationMessage: {ValidationMessage}\tSodPosition:{SodPosition}";
        }
    }

    public sealed class RejectedAmendmentClassMap : ClassMap<RejectedAmendment>
    {
        public RejectedAmendmentClassMap()
        {
            Map(x => x.SodPosition.S).Name("S");
            Map(x => x.SodPosition.F).Name("F");
            Map(x => x.SodPosition.P).Name("P");
            Map(x => x.SodPosition.PositionGroup).Name("PositionGroup");
            Map(x => x.SodPosition.AType).Name("AType");
            Map(x => x.SodPosition.OptionalQuantityCase, false).Name("Quantity").TypeConverter<OptionalQuantityOneofCaseTypeConverter>();
/*
// This works fine
.Convert(args =>
            {
                return (args.Value.SodPosition.GetQuantity() ?? 0).ToString(CultureInfo.InvariantCulture);
            });*/
            Map(x => x.SodPosition.C, false).Name("C");
            Map(x => x.SodPosition.PR, false).Name("PR");
            Map(x => x.SodPosition.FXR, false).Name("FXR");
            Map(x => x.ValidationMessage).Name("ValidationMessage");
        }
    }

    public class OptionalQuantityOneofCaseTypeConverter : ITypeConverter
    {
        public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
        {
            throw new System.NotImplementedException();
        }

        public string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
        {
            if (value is SodPosition sodPosition)
            {
                return (sodPosition.GetQuantity() ?? 0).ToString(CultureInfo.InvariantCulture);
            }

            return null;
        }
    }


protected string WriteCsv<T, TClassMap>(ICollection<T> data, string folder, string filename)
    where TClassMap : ClassMap<T>
{
    if (data == null) throw new ArgumentNullException(nameof(data));

    if (!data.Any())
    {
        throw new Exception($"data missing for {typeof(T)}");
    }

    if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentNullException(nameof(filename));

    var path = GetFullPath(folder, filename);

    if (path == null) throw new ArgumentNullException(nameof(path));

    if (_fileSystem.File.Exists(path))
    {
        throw new Exception($"File with name [{filename}] already exists at [{path}].");
    }

    using (var writer = new StreamWriter(path))
    {
        using (var csvWriter = new CsvWriter(writer, GetCsvConfiguration()))
        {
            csvWriter.Context.RegisterClassMap<TClassMap>();
            csvWriter.WriteHeader<T>();
            csvWriter.NextRecord();

            foreach (var record in data)
            {
                csvWriter.WriteRecord(record);
                csvWriter.NextRecord();
            }
        }
    }

    _log.Debug($"Saved file to path [{path}]");
    return path;
}

Expected behavior
Data to be written to the correct columns.

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
I experimented with the useExistingMap overload of Map(x => x...) to see if that had any bearing. It had a slight bearing in that if I also re-arranged a few columns, I could get the problem to improve (columns stopped appearing out of order), but the FXR column would still not write to the file.