Search code examples
c#csvhelper

Transform CSV input (1 field) to destination class (2 fields)


I'm writing my ClassMap which works for my first basic fields (including those with column name not matching class member). But I have 2 fields which need a particular work

1) I have a color stored as string. I need some code which convert the input to 2 values and store each one in a specific member.

2) I have an ID which match CSV item ID (that's the father or mother ID). But I need to convert it to the ID in my database (so I have to write some code to match CSV_ID to DB_ID).

Is it possible to add this custom logic with CSVHelper ? Thanks for help. Vincent


Solution

  • As it seems, CSVHelper also supports the same type converter injection during class mapping.

    https://joshclose.github.io/CsvHelper/examples/configuration/class-maps/type-conversion

    By combining this with the support for mapping by alternate names,

    https://joshclose.github.io/CsvHelper/examples/configuration/class-maps/mapping-by-alternate-names

    Having a csv file like this:

    Id,Name,Color
    1,OGUZ OZGUL,#f0f0f0
    2,VINCENT,#80A0C0
    3,OZGUL OGUZ,#00A000
    

    it is possible to achieve what's needed as follows:

    using System;
    using System.Globalization;
    using System.IO;
    using CsvHelper;
    using CsvHelper.Configuration;
    using CsvHelper.TypeConversion;
    using System.Linq;
    
    namespace console
    {
        public class Program
        {
            public class Foo
            {
                // Represents the database Id
                public int Id { get; set; }
                public string Name { get; set; }
                // Represents a three character color code, like #FFF
                public string Color3 { get; set; }
                // Represents a six character color code like #FFFFFF
                public string Color6 { get; set; }
            }
    
            // OK, we are not converting between types here, but who cares?
            // CSVHelper certainly doesn't.
            public class IdConverter : DefaultTypeConverter
            {
                public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
                {
                    string csvId = text;
                    int databaseId = Convert.ToInt32(text) + 10000;
                    return databaseId;
                }
    
                public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
                {
                    return ((int)value - 10000).ToString();
                }
            }
    
            // Again, we are changing the value as we wish, not the type.
            public class Color3Converter : DefaultTypeConverter
            {
                public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
                {
                    // format: #ffffff
                    return "#" + text[1] + text[3] + text[5];
                }
    
                public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
                {
                    // format: #fff
                    return "#" + ((string)value)[1] + "0" + ((string)value)[2] + "0" + ((string)value)[3] + "0";
                }
            }
    
            // By combining a type converter and alternative name
            // we achieve one CSV field value to be mapped to two properties
            // of our class Foo
            public sealed class FooMap : ClassMap<Foo>
            {
                public FooMap()
                {
                    Map(m => m.Id).TypeConverter<IdConverter>();
                    Map(m => m.Name);
                    Map(m => m.Color3).TypeConverter<Color3Converter>().Name("Color");
                    Map(m => m.Color6).Name("Color");
                }
            }
    
            static void Main(string[] args)
            {
                using (var reader = new StreamReader("data.csv"))
                {
                    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
                    {
                        csv.Configuration.RegisterClassMap<FooMap>();
                        Foo[] records = csv.GetRecords<Foo>().ToArray();
                        foreach(Foo record in records)
                        {
                            Console.WriteLine
                            (
                                "Foo, Id: {0}, Name: {1}, Color3: {2}, Color6: {3}",
                                 record.Id,
                                 record.Name,
                                 record.Color3,
                                 record.Color6  
                            );
                    }    
                    }
                }
            }
        }
    }
    

    The output of the program is:

    Foo, Id: 10001, Name: OGUZ OZGUL, Color3: #fff, Color6: #f0f0f0
    Foo, Id: 10002, Name: VINCENT, Color3: #8AC, Color6: #80A0C0
    Foo, Id: 10003, Name: OZGUL OGUZ, Color3: #0A0, Color6: #00A000