Строго типизированный парсинг CSV-файлов - PullRequest
0 голосов
/ 11 ноября 2018

Таким образом, после того, как в отчаянии я потратил около часа, я решил последовать совету всех присутствующих, и не реализовал мой собственный CSV-парсер .

Поэтому я выбрал FileHelpers .

Но у меня возникли проблемы с его правильным использованием.

Мой CSV-файл выглядит примерно так:

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

Моя потребность в FileHelpers (и, в частности, CsvEngine) находится в строке 3 - обратите внимание на третий столбец, заключенный в кавычки, поскольку он имеет внутреннюю запятую (которая в противном случае используется в качестве разделителя).

Мой код для чтения файла:

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

var records = ce.ReadFile(pathToCSVFile);

Работает нормально - вроде. Он правильно анализирует строки и распознает значения с заключенными в них разделителями.

Но.

Возвращаемое значение ReadFile() -метода равно object[]. И его содержимое выглядит как динамический тип.

Это выглядит примерно так - где столбцы названы "Field_1", "Field_2" и т. Д.

Automatically generated return type

Я создал «класс данных», предназначенный для хранения проанализированных строк. Это выглядит так:

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

Есть ли способ получить класс FileHelpers CsvEngine для возврата строго типизированных данных?

Если бы я мог просто использовать «базовый» парсер FileHelpers, я мог бы использовать этот код:

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

Есть ли способ получить CsvEngine возвращаемых экземпляров моего класса "Сотрудник"? Или я должен написать свой собственный код отображения для поддержки этого?

Ответы [ 4 ]

0 голосов
/ 13 ноября 2018

@ shamp00 имеет правильный ответ - и я также нашел его в FileHelper escape-разделитель .

Я взял свой модельный класс и украсил каждое свойство на нем, как было предложено:

(мне, вероятно, не нужно украшать все свойства, но пока это работает)

[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;
}

Теперь мне просто нужен этот код:

TextReader reader = new StreamReader(contents);
var engine = new FileHelperEngine<Employee>()
{
    Options = { IgnoreFirstLines = 1 }
};
var myRecords = engine.ReadStream(reader);
0 голосов
/ 11 ноября 2018

Если эта библиотека не работает, вы также можете попробовать использовать встроенный синтаксический анализатор .Net CSV TextFieldParser. Например: https://coding.abel.nu/2012/06/built-in-net-csv-parser/

ДОБАВЛЕНО: Для типов (с автоматическим преобразованием):

    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;}
    }
0 голосов
/ 12 ноября 2018

Использование CsvHelper в качестве жизнеспособной альтернативы и при условии, что файл CSV не имеет заголовков,

можно создать сопоставление для класса Employee, например

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

Где индекс сопоставлен с соответствующим свойством в строго типизированной объектной модели.

Чтобы использовать это сопоставление, вам необходимо зарегистрировать сопоставление в конфигурации.

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

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

    //...
}
0 голосов
/ 11 ноября 2018

Документация сработала для меня одним простым способом:

Сначала в вашем классе нужна пара декораторов:

Редактировать Используйте декоратор FieldQuoted для разбора чего-либо в кавычках и игнорирования включенной запятой

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

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

    public string State { get; set; }
}

DelimitedRecord для класса и ожидаемого разделителя (это может быть проблемой, если что-то изменится позже.

и FieldConverter для него появляется ничего, кроме строки.

Затем немного измените метод чтения:

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

и тогда все работает, строго набрал:

foreach(var person in readLines)
{
   Console.WriteLine(person.Name);
}
...