#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"
). Но упрощение будет полезно для людей без этих ограничений, тех, кто может просто разделить.