2

So after about an hour's worth of pulling my hair in desperation, I decided to follow the advice from, like, everybody in here and not implement my own CSV-parser.

So I went with FileHelpers instead.

But I am having a bit of trouble using it correctly.

My CSV-file looks something like this:

50382018,50319368,eBusiness Manager,IT02,3350_FIB4,IT,2480
50370383,50373053,CRM Manager,IT01,3200_FIB3,xyz,2480
50320067,50341107,"VP, Business Information Officer",IT03,3200_FI89,xyz,2480
50299061,50350088,Project Expert,IT02,8118_FI09,abc,2480

My need for FileHelpers (and, specifically CsvEngine) is in line 3 - notice third column enclosed in quotes since it has an internal comma (which is otherwise used as delimiter).

My code to read the file is this:

var co = new FileHelpers.Options.CsvOptions("Employee", columnDeliminator, 7);
var ce = new CsvEngine(co);

var records = ce.ReadFile(pathToCSVFile);

It works fine - sort of. It correctly parses the lines and recognizes the values with enclosed delimiters.

But.

The return value of the ReadFile()-method is object[]. And the contents of it appears to be some kind of dynamic type.

It looks something like this - where the columns are named "Field_1", "Field_2" etc.

Automatically generated return type

I have created a "data class" intended to hold the parsed lines It looks like this:

public class Employee
{
    public string DepartmentPosition;
    public string ParentDepartmentPosition;
    public string JobTitle;
    public string Role;
    public string Location;
    public string NameLocation;
    public string EmployeeStatus;
}

Is there a way to have FileHelpers' CsvEngine class to return strongly typed data?

If I could just use the "basic" parser of FileHelpers, I could use this code:

var engine = new FileHelperEngine<Employee>();
var records = engine.ReadFile("Input.txt");

Is there a way to have CsvEngine return instances of my "Employee" class? Or do I have to write my own mapping code to support this?

Jesper Lund Stocholm
  • 1,973
  • 2
  • 27
  • 49
  • Will the CSV file have headers? Have you looked at using [CsvHelper](https://joshclose.github.io/CsvHelper/)? – Nkosi Nov 11 '18 at 22:44

4 Answers4

1

@shamp00 has the correct answer - and I also found it at FileHelper escape delimiter .

I took my model class and decorated each property on it as suggested:

(I probably don't need to decorate all properties, but it works for now)

[DelimitedRecord((","))]
public class Employee
{
    [FieldQuoted('"', QuoteMode.OptionalForBoth)]
    public string DepartmentPosition;
    [FieldQuoted('"', QuoteMode.OptionalForBoth)]
    public string ParentDepartmentPosition;
    [FieldQuoted('"', QuoteMode.OptionalForBoth)]
    public string JobTitle;
    [FieldQuoted('"', QuoteMode.OptionalForBoth)]
    public string Role;
    [FieldQuoted('"', QuoteMode.OptionalForBoth)]
    public string Location;
    [FieldQuoted('"', QuoteMode.OptionalForBoth)]
    public string NameLocation;
    [FieldQuoted('"', QuoteMode.OptionalForBoth)]
    public string EmployeeStatus;
}

Now I just need this code:

TextReader reader = new StreamReader(contents);
var engine = new FileHelperEngine<Employee>()
{
    Options = { IgnoreFirstLines = 1 }
};
var myRecords = engine.ReadStream(reader);
Jesper Lund Stocholm
  • 1,973
  • 2
  • 27
  • 49
0

The documentation worked for me for a one simple way:

First in your class, it needs a couple decorators:

Edit Use the FieldQuoted decorator to parse anything in quotes and ignore the included comma

[DelimitedRecord(",")]
class Person
{
    [FieldQuoted]
    public string Name { get; set; }

    [FieldConverter(ConverterKind.Int32)]
    public int Age { get; set; }

    public string State { get; set; }
}

DelimitedRecord for the class and the expected delimiter (this could be a problem if things change later.

and FieldConverter for it appears anything other than string.

Then change your reading method slightly:

var fhr = new FileHelperEngine<Person>();            
var readLines = fhr.ReadFile(pathToFile);

and then it works, strongly typed:

foreach(var person in readLines)
{
   Console.WriteLine(person.Name);
}
Austin T French
  • 5,022
  • 1
  • 22
  • 40
  • Hi @Austin, you realize that you are suggesting that I try something, that I specifically wrote in my question as not being possible? . – Jesper Lund Stocholm Nov 11 '18 at 19:22
  • 2
    @Austin's answer looks correct to me. If you want the engine to return an array of concrete classes, you cannot use `CsvEngine`. You would need to do the mapping yourself. Using `FileHelperEngine` is the best approach, but you need to decorate the class with `[DelimitedRecord(",")]` and decorate the _JobTitle_ property with `[FieldQuoted(QuoteMode.OptionalForRead)]`. – shamp00 Nov 11 '18 at 20:22
  • Out of curiosity, @JesperLundStocholm why does that not work as a solution? Also, I tried to guess why it didn't, the "Quoted word, with comma" in which case I showed how to get around that. – Austin T French Nov 11 '18 at 21:07
  • @AustinTFrench I cannot use your suggestion since it chokes on line three with the enclosed delimiter. However - I found the solution and managed to augment your suggestion. See below :-) – Jesper Lund Stocholm Nov 12 '18 at 21:07
0

Using CsvHelper as a viable alternative and assuming the CSV file has no headers,

a mapping can be created for the Employee class like

public sealed class EmployeeClassMap : ClassMap<Employee> {
    public EmployeeClassMap() {
        Map(_ => _.Location).Index(0);
        Map(_ => _.NameLocation).Index(1);
        Map(_ => _.JobTitle).Index(2);
        //...removed for brevity
    }
}

Where the index is mapped to a respective property on the strongly typed object model.

To use this mapping, you need to register the mapping in the configuration.

using (var textReader = new StreamReader(pathToCSVFile)) {
    var csv = new CsvReader(textReader);
    csv.Configuration.RegisterClassMap<EmployeeClassMap>();

    var records = csv.GetRecords<Employee>();

    //...
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
-1

If this lib not work, you can also try to use built-in .Net CSV parser TextFieldParser. For ex: https://coding.abel.nu/2012/06/built-in-net-csv-parser/

ADDED: For types (with auto convert):

    static void run()
    {
        // split with any lib line of CSV
        string[] line = new string[]{"john", "doe", "201"};
        // needed prop names of class
        string[] propNames = "fname|lname|room".Split('|');

        Person p = new Person();
        parseLine<Person>(p, line, propNames);
    }

    static void parseLine<T>(T t, string[] line, string[] propNames)
    {
        for(int i = 0;i<propNames.Length;i++)
        {
            string sprop = propNames[i];
            PropertyInfo prop = t.GetType().GetProperty(sprop);
            object val = Convert.ChangeType(line[i], prop.PropertyType);
            prop.SetValue(t, val );
        }
    }

    class Person
    {
        public string fname{get;set;}
        public string lname{get;set;}
        public int room {get;set;}
    }
AndrewF
  • 33
  • 3