JoshClose / CsvHelper

Library to help reading and writing CSV files
http://joshclose.github.io/CsvHelper/
Other
4.76k stars 1.07k forks source link

Writing All Records with TypeConverter #977

Closed Stumpii closed 6 years ago

Stumpii commented 6 years ago

I have a CSV that uses Yes/No instead of True/False for booleans. I have configured the reader as such, which seemed to most efficient way and work great:

            '// Change default converter
            TypeConverterOptions typeConverterOptions = csv.Configuration.TypeConverterOptionsCache.GetOptions<bool>();
            typeConverterOptions.BooleanTrueValues.Add("Yes");
            typeConverterOptions.BooleanFalseValues.Add("No");'

Trouble comes when I write. The TypeConverterOptions are only for reading, not writing. I have tried a global TypeConverter as below:

            '// Change default converter
            csv.Configuration.TypeConverterCache.RemoveConverter<bool>();
            csv.Configuration.TypeConverterCache.AddConverter<bool>(new MyBooleanConverter());'

However this does not work, my assumption being that I am writing with 'WireRecords' which does not appear to call the type converter. If I apply the typeconverter in mapping:

            'Map(m => m.Logged).Index(3).TypeConverter<MyBooleanConverter>();'

Then it does work. However I have 17 maps, each with multiple bool values, so I really want to specify at the global level, not the mapping level.

Is my assumption correct and/or is there a work around?

Thanks, Steve

JoshClose commented 6 years ago

The BooleanConverter doesn't do anything special for writing, so you'll have to implement your own that overrides the ConvertToString method and outputs what you want. You can inherit from BooleanConverter still to get the reading part for free.

Based on your code, it looks like you may have tried this already. You should be able to globally set a type converter for a type.

void Main()
{
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream))
    using (var reader = new StreamReader(stream))
    using (var csv = new CsvWriter(writer))
    {
        var records = new List<Test>
        {
            new Test { Id = 1, Name = "one", IsTrue = true },
            new Test { Id = 2, Name = "two", IsTrue = false },
        };

        csv.Configuration.TypeConverterCache.AddConverter<bool>(new YesNoConverter());
        csv.WriteRecords(records);

        writer.Flush();
        stream.Position = 0;

        reader.ReadToEnd().Dump();
    }
}

public class Test
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsTrue { get; set; }
}

public class YesNoConverter : BooleanConverter
{
    public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
    {       
        //var boolValue = value as bool?;
        if (value is bool boolValue)
        {
            return boolValue ? "Yes" : "No";
        }

        return base.ConvertToString(value, row, memberMapData);
    }
}

AddConverter will replace an existing converter.

Stumpii commented 6 years ago

Thanks. I had ended up with basically the same thing, then included ConvertFromString so that conversions both way are together:

    internal class BooleanYesNoConverter : BooleanConverter
    {
        public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
        {
            switch (text.ToLower())
            {
                case "yes":
                    return true;

                case "no":
                    return false;
            }

            return base.ConvertFromString(text, row, memberMapData);
        }

        public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
        {
            if (value is bool boolValue)
            {
                return boolValue ? "Yes" : "No";
            }

            return base.ConvertToString(value, row, memberMapData);
        }
    }

I also switched to using attributes to set which fields use the converter, instead of a global converter. I might try your way.

By the way, thanks for the great library.

JoshClose commented 6 years ago

Awesome and you're welcome!