How to use a dynamic CSV delimiter with FileHelpers?

Stefan Steiger picture Stefan Steiger · Jan 4, 2012 · Viewed 14.7k times · Source

Question: I need to read a CSV file. I use the FileHelpers library to achieve this.

The problem is I need a dynamic delimiter (user defined), meaning anything can be delimiter (Comma, semicolon, tab, newline, but also anything else).

The problem is, FileHelpers defines the delimiter in an attribute, which means at compile-time. This makes it impossible to do it dynamically.

What I can do is declare a new class, which inherits from one base class, and set the delimiter on this new class.

[FileHelpers.DelimitedRecord(",")]
public class CommaCustomer : BaseCustomer
{

}

That way I only have to make changes in the base class for every new delimiter. The problem is, this is I can't (and don't want to) create a child class for every possible delimiter.

This is the code I have so far:

using System;
using System.Data;
using System.IO;
//using FileHelpers;
//using FileHelpers.RunTime;


namespace Examples
{


    class MainClass
    {


        [STAThread]
        static void Main()
        {
            FileHelpers.FileHelperEngine engine = new FileHelpers.FileHelperEngine(typeof(SemicolonCustomer));

            // To read use:

            string str = @"D:\Username\Desktop\FileHelpers_Examples_CSharp_VbNet\Data\SemicolonCustomers.txt";
            //str = @"D:\Username\Desktop\FileHelpers_Examples_CSharp_VbNet\Data\CustomersDelimited.txt";
            SemicolonCustomer[] custs = (SemicolonCustomer[])engine.ReadFile(str);
            //Customer[] custs = (Customer[]) engine.ReadFile("yourfile.txt");


            foreach (SemicolonCustomer cli in custs)
            {
                Console.WriteLine();
                Console.WriteLine("Customer: " + cli.CustId.ToString() + " - " + cli.Name);
                Console.WriteLine("Added Date: " + cli.AddedDate.ToString("d-M-yyyy"));
                Console.WriteLine("Balance: " + cli.Balance.ToString());
                Console.WriteLine();
                Console.WriteLine("-----------------------------");
            } // Next cli

            Console.ReadKey();
            Console.WriteLine("Writing data to a delimited file...");
            Console.WriteLine();


            // To write use:
            //engine.WriteFile("myyourfile.txt", custs);


            //If you are using .NET 2.0 or greater is 
            //better if you use the Generics version:

            // FileHelperEngine engine = new FileHelperEngine<Customer>();

            // To read use (no casts =)
            // Customer[] custs = engine.ReadFile("yourfile.txt");

            // To write use:
            // engine.WriteFile("yourfile.txt", custs);

        } // End Sub Main


    } // End Class  MainClass


    //------------------------
    //   RECORD CLASS (Example, change at your will)
    //   TIP: Remember to use the wizard to generate this class
    public class BaseCustomer
    {
        public int CustId;

        public string Name;
        public decimal Balance;
        [FileHelpers.FieldConverter(FileHelpers.ConverterKind.Date, "ddMMyyyy")]
        public DateTime AddedDate;
    }


    [FileHelpers.DelimitedRecord(";")]
    public class SemicolonCustomer : BaseCustomer
    {

    }


    [FileHelpers.DelimitedRecord(",")]
    public class CommaCustomer : BaseCustomer
    {

    }


}

Is it somehow possible at runtime to compile a child class

[FileHelpers.DelimitedRecord(\"" + delimiter + "\")]
public class AnyDelimiterCustomer : BaseCustomer
{           
}

And then reference this runtime compiled class in code ?

Answer

shamp00 picture shamp00 · Feb 2, 2012

I just realized there is a DelimitedFileEngine which solves your problem another way.

You can just go

var engine = new DelimitedFileEngine(typeof(BaseCustomer));
engine.Options.Delimiter = ",";

It seems that BaseCustomer needs to be decorated with a [DelimitedRecord] attribute, otherwise an exception is raised but the delimiter is overridden by whatever is supplied to engine.Options.Delimiter.

The following example imports a comma delimited record using a format which is marked as bar delimited.

[DelimitedRecord("|")]
public class Format1
{
    public string Field1;           
    public string Field2;            
    public string Field3;            
    public string Field4;
}

static void Main(string[] args)
{
    var engine = new DelimitedFileEngine(typeof(Format1));
    // change the delimiter
    engine.Options.Delimiter = ","; 

    // import a comma separated record
    object[] importedObjects = engine.ReadString(@"a,b,c,d");

    foreach (object importedObject in importedObjects)
    {
        if (importedObject is Format1)
        {
            Format1 format1 = (Format1)importedObject;
            // process it (for example, check the values)
            Assert.AreEqual("a", format1.Field1);
            Assert.AreEqual("b", format1.Field2);
            Assert.AreEqual("c", format1.Field3);
            Assert.AreEqual("d", format1.Field4);
        }
    }
}