Строго типизированный парсинг CSV-файлов

2

Поэтому, потратив примерно час на то, чтобы в отчаянии дернуть себя за волосы, я решил последовать совету всех присутствующих и не реализовывать свой собственный 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() -method является object[]. И его содержимое выглядит как динамический тип.

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

Изображение 174551

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

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

Есть ли способ, чтобы класс CsvEngine возвращал строго типизированные данные?

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

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

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

  • 0
    Будут ли в файле CSV заголовки? Вы смотрели на использование CsvHelper ?
Теги:
csv
text-parsing
filehelpers

4 ответа

0
Лучший ответ

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

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

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

[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

Используя 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

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

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

Редактировать Используйте декоратор 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);
}
  • 2
    @ Ответ Остина мне кажется правильным. Если вы хотите, чтобы движок возвращал массив конкретных классов, вы не можете использовать CsvEngine . Вы должны сделать картографирование самостоятельно. Использование FileHelperEngine<Employee> - лучший подход, но вам нужно украсить класс с помощью [DelimitedRecord(",")] и украсить свойство JobTitle с помощью [FieldQuoted(QuoteMode.OptionalForRead)] .
  • 0
    Из любопытства, @JesperLundStocholm, почему это не работает как решение? Кроме того, я попытался угадать, почему это не так, «цитируемое слово с запятой», и в этом случае я показал, как обойти это.
Показать ещё 2 комментария
-2

Если эта библиотека не работает, вы также можете попробовать использовать встроенный синтаксический анализатор .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
    Это действительно комментарий, а не большой ответ (или, возможно, ответ только по ссылке)
  • 0
    не могу писать комментарии :(

Ещё вопросы

Сообщество Overcoder
Наверх
Меню