c# - serialize - que es serializar y deserializar json
Mejores prácticas para serializar objetos a un formato de cadena personalizado para usar en un archivo de salida (6)
Aquí hay una manera genérica para crear CSV a partir de una lista de objetos, utilizando la reflexión:
public static string ToCsv<T>(string separator, IEnumerable<T> 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();
}
Se pueden realizar muchas variaciones, como escribir directamente en un archivo en ToCsv (), o reemplazar el StringBuilder con un IEnumerable y declaraciones de rendimiento.
Estaba a punto de implementar una anulación de ToString () en una clase de negocios en particular para producir un formato compatible con Excel para escribir en un archivo de salida, que se recogerá más tarde y se procesará. Esto es lo que se supone que deben ser los datos:
5555555 "LASTN SR, FIRSTN" 5555555555 13956 STREET RD TOWNSVILLE MI 48890 25.88 01-003-06-0934
No es gran cosa para mí hacer una cadena de formato y anular ToString()
, pero eso cambiará el comportamiento de ToString()
para cualquier objeto que decida serializar de esta manera, haciendo que la implementación de ToString()
desigual en toda la biblioteca .
Ahora, he estado leyendo en IFormatProvider , y una clase que lo implementa suena como una buena idea, pero todavía estoy un poco confundido acerca de dónde debe residir toda esta lógica y cómo construir la clase de formateador.
¿Qué hacen ustedes cuando necesitan hacer un CSV, delimitado por tabuladores o alguna otra cadena arbitraria no XML de un objeto?
Aquí hay una versión simplificada de la idea de CSV de Per Hejndorf (sin la sobrecarga de memoria ya que produce cada línea a su vez). Debido a la demanda popular, también admite campos y propiedades simples mediante el uso de Concat
.
Actualización 18 de mayo de 2017
Este ejemplo nunca tuvo la intención de ser una solución completa, solo avanzaba la idea original publicada por Per Hejndorf. Para generar un CSV válido, debe reemplazar cualquier carácter delimitador de texto, dentro del texto, con una secuencia de 2 caracteres delimitadores. por ejemplo, un simple .Replace("/"", "/"/"")
.
Actualización 12 de febrero de 2016
Después de utilizar mi propio código nuevamente en un proyecto de hoy, me di cuenta de que no debería haber dado nada por sentado cuando comencé con el ejemplo de @Per Hejndorf
. Tiene más sentido suponer un delimitador predeterminado de "," (coma) y hacer del delimitador el segundo parámetro opcional . Mi propia versión de biblioteca también proporciona un tercer parámetro de header
que controla si se debe devolver una fila de encabezado, ya que a veces solo quiere los datos.
p.ej
public static IEnumerable<string> ToCsv<T>(IEnumerable<T> 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());
}
}
entonces, úsela así para comas delimitadas:
foreach (var line in ToCsv(objects))
{
Console.WriteLine(line);
}
o como este para otro delimitador (por ejemplo, TAB):
foreach (var line in ToCsv(objects, "/t"))
{
Console.WriteLine(line);
}
Ejemplos prácticos
escribir lista en un archivo CSV delimitado por comas
using (TextWriter tw = File.CreateText("C:/testoutput.csv"))
{
foreach (var line in ToCsv(objects))
{
tw.WriteLine(line);
}
}
o escribirlo delimitado por tabuladores
using (TextWriter tw = File.CreateText("C:/testoutput.txt"))
{
foreach (var line in ToCsv(objects, "/t"))
{
tw.WriteLine(line);
}
}
Si tiene campos / propiedades complejos, deberá filtrarlos de las cláusulas de selección.
Versiones anteriores y detalles a continuación:
Aquí hay una versión simplificada de la idea CSV de Per Hejndorf (sin la sobrecarga de memoria ya que produce cada línea por turno) y tiene solo 4 líneas de código :)
public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> 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());
}
}
Puedes iterarlo así:
foreach (var line in ToCsv(",", objects))
{
Console.WriteLine(line);
}
donde los objects
son una lista fuertemente tipada de objetos.
Esta variación incluye tanto campos públicos como propiedades públicas simples:
public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> 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());
}
}
Como regla general, abogo solo por anular a String como una herramienta para la depuración, si es por lógica de negocios, debería ser un método explícito en la clase / interfaz.
Para una serialización simple como esta, sugiero tener una clase separada que sepa sobre su biblioteca de salida CSV y sus objetos de negocio que hace la serialización en lugar de empujar la serialización en los propios objetos comerciales.
De esta forma terminas con una clase por formato de salida que produce una vista de tu modelo.
Para una serialización más compleja en la que intentas escribir un gráfico de objetos para persistencia, consideraría incluirlo en las clases de negocios, pero solo si se trata de un código más limpio.
El problema con las soluciones que encontré hasta ahora es que no permiten exportar un subconjunto de propiedades, sino solo el objeto completo. La mayoría de las veces, cuando necesitamos exportar datos en CSV, necesitamos "adaptar" su formato de manera precisa, así que creé este método de extensión simple que me permite hacer eso pasando una matriz de parámetros de tipo Func<T, string>
para especificar la asignación.
public static string ToCsv<T>(this IEnumerable<T> list, params Func<T, string>[] 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();
}
Uso:
philosophers.ToCsv(x => x.LastName, x => x.FirstName)
Genera:
Hayek,Friedrich
Rothbard,Murray
Brent,David
La respuesta de Gone Coding fue muy útil. Hice algunos cambios para manejar gremlins de texto que manguera la salida.
/******************************************************/
public static IEnumerable<string> ToCsv<T>(IEnumerable<T> 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;
}
}
Tuve un problema: la variación de HiTech Magic eran dos propiedades con el mismo valor, solo una se completaría. Esto parece haberlo solucionado:
public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> 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());
}
}