Рекомендации по сериализации объектов в пользовательский строковый формат для использования в выходном файле

Я как раз собирался реализовать переопределение ToString () в конкретном бизнес-classе, чтобы создать удобный для Excel формат для записи в выходной файл, который будет получен позже и обработан. Вот как должны выглядеть данные:

5555555 "LASTN SR, FIRSTN" 5555555555 13956 STREET RD TOWNSVILLE MI 48890 25.88 01-003-06-0934 

Для меня просто не нужно просто форматировать строку и переопределять ToString() , но это изменит поведение ToString() для любых объектов, которые я решил сериализовать таким образом, делая реализацию ToString() все оборванной через библиотеку ,

Теперь я читал на IFormatProvider , и class, реализующий его, звучит как хорошая идея, но я все еще немного смущен тем, где должна находиться вся эта логика и как построить class форматирования.

Что вы делаете, когда вам нужно сделать CSV, табуляцию или какую-либо другую не-XML произвольную строку из объекта?

Вот общий способ создания CSV из списка объектов, используя reflection:

  public static string ToCsv(string separator, IEnumerable objectlist) { Type t = typeof(T); FieldInfo[] fields = t.GetFields(); string header = String.Join(separator, fields.Select(f => f.Name).ToArray()); StringBuilder csvdata = new StringBuilder(); csvdata.AppendLine(header); foreach (var o in objectlist) csvdata.AppendLine(ToCsvFields(separator, fields, o)); return csvdata.ToString(); } public static string ToCsvFields(string separator, FieldInfo[] fields, object o) { StringBuilder linie = new StringBuilder(); foreach (var f in fields) { if (linie.Length > 0) linie.Append(separator); var x = f.GetValue(o); if (x != null) linie.Append(x.ToString()); } return linie.ToString(); } 

Могут быть внесены многие изменения, например, выписывание непосредственно в файл в ToCsv () или замена StringBuilder на инструкции IEnumerable и yield.

Вот упрощенная версия идеи CSV Пер Хейндорфа (без накладных расходов на память, поскольку она дает каждую строку по очереди). Благодаря популярному спросу он также поддерживает как поля, так и простые свойства с помощью Concat .

Обновление 18 мая 2017 года

Этот пример никогда не был предназначен для полного решения, просто продвигая оригинальную идею, опубликованную Пер Хейндорфом. Для генерации правильного CSV вам нужно заменить любые текстовые разделители, в тексте, последовательностью из двух символов разделителя. например, простой .Replace("\"", "\"\"") .

Обновление 12 февраля 2016 года

После использования моего собственного кода снова в проекте сегодня, я понял, что не должен был принимать что-то само собой разумеющееся, когда я начал с примера @Per Hejndorf . Имеет смысл предположить разделитель по умолчанию «,» (запятая) и сделать разделитель вторым необязательным параметром. Моя собственная версия библиотеки также предоставляет третий параметр header который определяет, следует ли возвращать строку заголовка, поскольку иногда вам нужны только данные.

например

 public static IEnumerable ToCsv(IEnumerable objectlist, string separator = ",", bool header = true) { FieldInfo[] fields = typeof(T).GetFields(); PropertyInfo[] properties = typeof(T).GetProperties(); if (header) { yield return String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p=>p.Name)).ToArray()); } foreach (var o in objectlist) { yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString()) .Concat(properties.Select(p=>(p.GetValue(o,null) ?? "").ToString())).ToArray()); } } 

поэтому вы затем используете его так же, как для запятой:

 foreach (var line in ToCsv(objects)) { Console.WriteLine(line); } 

или как это для другого разделителя (например, TAB):

 foreach (var line in ToCsv(objects, "\t")) { Console.WriteLine(line); } 

Практические примеры

записать список в CSV-файл с разделителями-запятыми

 using (TextWriter tw = File.CreateText("C:\testoutput.csv")) { foreach (var line in ToCsv(objects)) { tw.WriteLine(line); } } 

или написать его с разделителями табуляции

 using (TextWriter tw = File.CreateText("C:\testoutput.txt")) { foreach (var line in ToCsv(objects, "\t")) { tw.WriteLine(line); } } 

Если у вас есть сложные поля / свойства, вам нужно будет отфильтровать их из предложений select.


Предыдущие версии и данные ниже:

Вот упрощенная версия идеи CSV Пер Хейндорфа (без накладных расходов памяти, поскольку она дает каждую строку по очереди) и имеет только 4 строки кода 🙂

 public static IEnumerable ToCsv(string separator, IEnumerable objectlist) { FieldInfo[] fields = typeof(T).GetFields(); yield return String.Join(separator, fields.Select(f => f.Name).ToArray()); foreach (var o in objectlist) { yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString()).ToArray()); } } 

Вы можете повторить так:

 foreach (var line in ToCsv(",", objects)) { Console.WriteLine(line); } 

где objects – это строго типизированный список объектов.

Этот вариант включает как публичные поля, так и простые общедоступные свойства:

 public static IEnumerable ToCsv(string separator, IEnumerable objectlist) { FieldInfo[] fields = typeof(T).GetFields(); PropertyInfo[] properties = typeof(T).GetProperties(); yield return String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p=>p.Name)).ToArray()); foreach (var o in objectlist) { yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString()) .Concat(properties.Select(p=>(p.GetValue(o,null) ?? "").ToString())).ToArray()); } } 

Как эмпирическое правило я отстаиваю только переопределение toString в качестве инструмента для отладки, если это для бизнес-логики, он должен быть явным методом для classа / интерфейса.

Для простой сериализации, как это, я бы предложил иметь отдельный class, который знает о вашей библиотеке вывода CSV и ваших бизнес-объектах, которые выполняют сериализацию, а не толкают сериализацию к самим бизнес-объектам.

Таким образом, вы получаете class для каждого формата вывода, который дает представление о вашей модели.

Для более сложной сериализации, когда вы пытаетесь записать граф объектов для сохранения, я бы подумал о том, чтобы положить его в бизнес-classы, но только если он делает более чистый код.

У меня возникла проблема, когда вариация HiTech Magic была двумя свойствами с одинаковым значением, только один заселялся. Кажется, это исправлено:

  public static IEnumerable ToCsv(string separator, IEnumerable objectlist) { FieldInfo[] fields = typeof(T).GetFields(); PropertyInfo[] properties = typeof(T).GetProperties(); yield return String.Join(separator, fields.Select(f => f.Name).Union(properties.Select(p => p.Name)).ToArray()); foreach (var o in objectlist) { yield return string.Join(separator, (properties.Select(p => (p.GetValue(o, null) ?? "").ToString())).ToArray()); } } 

Ответ Gone Coding был очень полезен. Я внес некоторые изменения в него, чтобы обрабатывать текстовые гремлины, которые будут выдавать результат.

  /******************************************************/ public static IEnumerable ToCsv(IEnumerable objectlist, string separator = ",", bool header = true) { FieldInfo[] fields = typeof(T).GetFields(); PropertyInfo[] properties = typeof(T).GetProperties(); string str1; string str2; if(header) { str1 = String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p => p.Name)).ToArray()); str1 = str1 + Environment.NewLine; yield return str1; } foreach(var o in objectlist) { //regex is to remove any misplaced returns or tabs that would //really mess up a csv conversion. str2 = string.Join(separator, fields.Select(f => (Regex.Replace(Convert.ToString(f.GetValue(o)), @"\t|\n|\r", "") ?? "").Trim()) .Concat(properties.Select(p => (Regex.Replace(Convert.ToString(p.GetValue(o, null)), @"\t|\n|\r", "") ?? "").Trim())).ToArray()); str2 = str2 + Environment.NewLine; yield return str2; } } 

Проблема с решениями, которые я нашел до сих пор, заключается в том, что они не позволяют экспортировать подмножество свойств, а только весь объект. В большинстве случаев, когда нам нужно экспортировать данные в CSV, нам нужно «точно настроить» его формат, поэтому я создал этот простой метод расширения, который позволяет мне это сделать, передав массив параметров типа Func чтобы указать отображение.

 public static string ToCsv(this IEnumerable list, params Func[] properties) { var columns = properties.Select(func => list.Select(func).ToList()).ToList(); var stringBuilder = new StringBuilder(); var rowsCount = columns.First().Count; for (var i = 0; i < rowsCount; i++) { var rowCells = columns.Select(column => column[i]); stringBuilder.AppendLine(string.Join(",", rowCells)); } return stringBuilder.ToString(); } 

Применение:

 philosophers.ToCsv(x => x.LastName, x => x.FirstName) 

Формирует:

 Hayek,Friedrich Rothbard,Murray Brent,David 
  • Исключить свойство из сериализации через пользовательский атрибут (json.net)
  • Каковы различия между XmlSerializer и BinaryFormatter
  • Исключение «Исключительная привязка к реляционной ссылке» с JSON.Net
  • Сериализация jacksonа JSON, предотrotation рекурсии путем определения уровня
  • Как получить json.net для сериализации членов classа, полученных из List ?
  • JSON.Net выдает StackOverflowException при использовании
  • Использование JSON.NET для возврата ActionResult
  • Как вы сериализуете объект на C ++?
  • Django rest framework, используйте разные сериализаторы в том же ModelViewSet
  • Использование readObject / writeObject в сериализации
  • Информация о сериализации типов кеша Json.NET?
  • Давайте будем гением компьютера.