Использование CSVHelper как десериализовать CSV со списком подпунктов

#c# #csv #csvhelper

#c# #csv #csvhelper

Вопрос:

Вопрос:

Присваивается объекту FooBar , который содержит список Bar , с FooBar и Bar определяет как таковой:

 class FooBar{
    int FooID {get;set;}
    string FooProperty1 {get;set;}
    List<Bar> Bars {get;set;};
}

class Bar{
    int BarID {get;set;}    
    string BarProperty1 {get;set;}  
    string BarProperty2 {get;set;}  
    string BarProperty3 {get;set;}
}
 

Я получаю следующий CSV в качестве входных данных:

 1,FooProperty1,BarID_1,BarProperty1_1,BarProperty2_1,BarProperty3_1,BarID_2,BarProperty1_2,BarProperty2_2,BarProperty3_2
 

Где поля BarID, BarProperty1, BarProperty2, BarProperty3 имеют суффикс по их индексу в коллекции.

Как мне десериализовать этот ввод в мой объект?

введите описание изображения здесь


пример ввода:

1 экземпляр FooBar и 2 вложенных бара: 1,FooProperty1,BarID_1,BarProperty1_1,BarProperty2_1,BarProperty3_1,BarID_2,BarProperty1_2,BarProperty2_2,BarProperty3_2

1 экземпляр FooBar, но без панели:
1,FooProperty1


Пытается:

Я попробовал использовать Convert, чтобы сопоставить эти свойства с новым экземпляром Bar, например :

 public class FooBarMap : ClassMap<FooBar> 
{
    public FooBarMap()
    {
        Map(m => m.FooID);
        Map(m => m.Bars).ConvertUsing(row =>
        {            
            var list = new List<Bar>
            {
                new Bar { 
                    BarProperty1 = row.GetField("BarProperty1_1"), 
                    BarProperty2 = row.GetField("BarProperty2_1"),
                    // .. Other Properties
                },
                new Bar {}, //.. Same on _2
            };
            return list;
        });
    }
}
 

Конечно, никакого контроля над вводом. Я бы отправлял Json / Xml, а не CSV.

Комментарии:

1. Извините за Json.et вместо помощника CSV и неправильного тега. должно быть, пятница.

2. Разве свойства панели не были бы лучше в виде массива в вашей модели C #, а не отдельных свойств? Похоже, что все они являются связанными элементами одного и того же типа

3. @Fubo, просто опечатка из упрощения. Не обращайте на это внимания. Я отредактирую как можно скорее.

4. @ADyson, но это name, cust, org, я просто переименовал их в [className] Свойство [Number] . Это не Dict или массив строк. Это не все строки, просто строка проще для MCVE

5. Но имена свойств не являются динамическими. Я знаю их (более 20), и шаблон всегда будет «[KnowPropertyName]_[Index 1]».

Ответ №1:

Это возможно с помощью пользовательского конвертера типов, но сложно.

Вам нужно украсить свойство Index атрибутом (даже если он не используется).

 public class FooBar
{
    [Index(2)]
    public List<Bar> Bars { get; set; }
}
 

Конвертер используется как для чтения, так и для записи, поэтому вам необходимо переопределить два метода:

   public class BarListConverter : DefaultTypeConverter
  {
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
      var list = new List<Bar>();
      if (text == null) return list;
      do
      {
        var barIndex = list.Count   1;
        var bar = new Bar
        {
          BarID = row.GetField<int>($"BarID_{barIndex}"),
          BarProperty1 = row.GetField<string>($"BarProperty1_{barIndex}"),
          BarProperty2 = row.GetField<string>($"BarProperty2_{barIndex}"),
          BarProperty3 = row.GetField<string>($"BarProperty3_{barIndex}")
        };
        list.Add(bar);
      } while (row.Context.CurrentIndex > 0 amp;amp; row.Context.CurrentIndex < row.Context.Record.Length - 1);
      return list;
    }

    public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
    {
      var bars = value as List<Bar>;
      if (bars == null) return null;
      foreach (var bar in bars)
      {
        row.WriteField(bar.BarID);
        row.WriteField(bar.BarProperty1);
        row.WriteField(bar.BarProperty2);
        row.WriteField(bar.BarProperty3);
      }
      return null;
    }
  }
}
 

Чтение:

   public List<FooBar> Reading()
  {
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream))
    using (var reader = new StreamReader(stream))
    using (var csv = new CsvReader(reader))
    {
      writer.WriteLine(
        "FooID,FooProperty1,BarID_1,BarProperty1_1,BarProperty2_1,BarProperty3_1,BarID_2,BarProperty1_2,BarProperty2_2,BarProperty3_2");
      writer.WriteLine("1,Foo1,1,2,3,4,5,6,7,8");
      writer.Flush();
      stream.Position = 0;

      csv.Configuration.HeaderValidated = null;
      csv.Configuration.MissingFieldFound = null;
      csv.Configuration.TypeConverterCache.AddConverter<List<Bar>>(new BarListConverter());

      return csv.GetRecords<FooBar>().ToList();
    }
  }
 

Написание:

   public string Writing(List<FooBar> data)
  {
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream))
    using (var reader = new StreamReader(stream))
    using (var csv = new CsvWriter(writer))
    {
      csv.Configuration.HasHeaderRecord = false;
      csv.Configuration.TypeConverterCache.AddConverter<List<Bar>>(new BarListConverter());
      csv.WriteRecords(data);
      writer.Flush();
      stream.Position = 0;

      return reader.ReadToEnd();
    }
 

Комментарии:

1. Это классическая проблема, которую нелегко найти. Я надеюсь, что вознаграждение придаст этому ответу большую наглядность.

Ответ №2:

Я создал очень упрощенный метод, который будет перебирать пары ключ / значение…

 private static FooBar Parse(string value)
{
    // a basic check for null or empty string
    if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value));

    // split the string
    string[] split = value.Split(',');

    // a basic check for properly formed key/value pairs in the string
    if (split.Length < 2 || split.Length % 2 != 0)
        throw new ArgumentException("Malformed string.", nameof(value));

    // put the values into our object
    var result = new FooBar();

    // Foo pair
    result.FooID = Int32.Parse(split[0]);
    result.FooProperty1 = split[1];

    // Bar pairs
    result.Bars = new List<Bar>();
    for (int i = 2; i < split.Length; i = i   4)
    {
        result.Bars.Add(new Bar()
        {
            BarID = split[i],
            BarProperty1 = split[i 1],
            BarProperty2 = split[i 2],
            BarProperty3 = split[i 3]
        });
    }

    return resu<
}
 

Я сопоставил это с двумя примерами следующим образом

 string value = "1,FooProperty1";
FooBar result = Parse(value); // works
 

и

 string value = "1,FooProperty1,BarID_1,BarProperty1_1,BarProperty2_1,BarProperty3_1,BarID_2,BarProperty1_2,BarProperty2_2,BarProperty3_2";
FooBar result = Parse(value); // works
 

обратите внимание, что ваш класс необходимо немного обновить, чтобы

 public class FooBar
{
    public int FooID { get; set; }
    public string FooProperty1 { get; set; }
    public List<Bar> Bars { get; set; }
}

public class Bar
{
    public string BarID { get; set; } // you originally had this as int
    public string BarProperty1 { get; set; }
    public string BarProperty2 { get; set; }
    public string BarProperty3 { get; set; }
}
 

Мне этот вариант кажется гораздо менее запутанным и в целом более легким для чтения. Я не вижу особых причин, по которым вы должны принудительно выполнять преобразование с помощью других средств, таких как использование класса CSVHelper.

Комментарии:

1. Csv в моем примере чистый и упрощенный. Но это настоящий CSV от реального клиента. это означает, что Csv helper необходим для синтаксического анализа. Многострочные данные, экранированный разделитель столбцов (например: "foo;";"foo,bar" ). Но упрощение будет полезно для людей без этих ограничений, тех, кто может просто разделить.