Cómo leer y escribir una lista de objetos a csv en java
Java es un lenguaje que he utilizado en incontables ocasiones para realizar scripts pequeños con el objetivo de procesar datos desde ciertos orígenes y terminar generando un resultado en algún formato de salida. En muchas ocasiones ese formato es CSV pero siempre acabo buscando en google una forma rápida de exportar mis datos a este formato en java. Este post está dedicado a mi yo futuro el día que vuelva a tener una lista de objetos y quiera generar un CSV. Este post asume el formato que más he empleado a la hora de exportar a CSV.
-
El separador son comas.
-
Se han de incluir cabeceras.
-
El orden de las cabeceras ha de ser el mismo que el decalrado en el POJO.
-
Los valores de tipo String han sido escapados usando comillas.
-
El separador de decimales es el punto.
-
Las fechas han de estar en formato ISO8601.
-
No se han de tener que añadir anotaciones extra en el código.
Para el ejemplo, usaremos datos de acceso de usuarios a un sistema con su fecha, su numero de intentos y el riesgo detectado en dicho acceso:
name,accessTime,attempts,risk
Graehme,2022-01-28T22:57:55Z,3,0.74
Leroi,2022-08-25T22:28:22Z,2,0.5
Christin,2022-04-09T04:24:20Z,3,0.83
Field,2022-01-22T11:00:50Z,2,0.08
....
Importar jackson csv
Jackson, el famoso serializador de Json en java soporta multitud de otros formatos, csv es uno de ellos, la forma más sencilla es importarlo usando maven y copiando la úiltima versión disponible, en este caso vamos a usar.
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.14.1</version>
</dependency>
Crear la clase contenedora de los datos
El primer paso es tener una clase que represente los datos a exportar, en este caso se ha usado @Data de lombok para reducir el constructor, getters, setter, etc
@Data
public class AccessRecord {
private String name;
private Instant access;
private Integer attempts;
private Double risk;
}
Escribir a CSV
final List<AccessRecord> accesos = new ArrayList<>(); // 1
final CsvMapper mapper = CsvMapper.builder() // 2
.findAndAddModules() // 2.1
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 2.2
.disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) // 2.3
.build();
StringWriter resultado = new StringWriter(); // 3
try (StringWriter destino = resultado) { // 4
mapper.writer(mapper.schemaFor(AccessRecord.class).withHeader()) // 5
.writeValues(destino) // 6
.writeAll(accesos); // 7
}
System.out.println(resultado);
-
Conjunto de datos a escribir.
-
Creamos el mapper para escribir en CSV.
-
Registramos los módulos extra, en este caso jsr310 para poder escribir tiempos.
-
Indicamos que las fechas se han de visualizar en formato texto y no mo timestamps.
-
Configuramos el mapper para que no ordene alfabeticamente los capos al escribir.
-
-
Configuramos nuestro destino, en este caso un string en memoria pero puede ser un File, un OutputStream, etc.
-
Al tratarse de recursos usamos el tryWithResources para cerrarlos al final de la escritura y asegurar que se ha realizado un flush de todo el contenido de los mismos.
-
Configuramos un writer indicando que queremos cabeceras.
-
Establecemos donde vamos a querer escribir.
-
Escribimos la lista de paises.
Leer CSV
String input = """
name,accessTime,attempts,risk
Graehme,2022-01-28T22:57:55Z,3,0.74
Leroi,2022-08-25T22:28:22Z,2,0.5
Christin,2022-04-09T04:24:20Z,3,0.83
Field,2022-01-22T11:00:50Z,2,0.08
Sib,2022-02-02T02:42:17Z,1,0.98
Brandise,2022-03-13T03:46:24Z,2,0.9
Opalina,2022-10-10T00:44:42Z,2,0.71
Warde,2022-08-25T15:15:49Z,3,0.96
Vinnie,2022-08-03T10:25:55Z,2,0.19
Gaspar,2022-09-10T07:06:58Z,1,0.71
Euell,2022-07-03T09:04:06Z,2,0.84
"""; // 1
final CsvMapper mapper = CsvMapper.builder() // 2
.findAndAddModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
.build();
try (MappingIterator<AccessRecord> iterator = mapper
.readerFor(AccessRecord.class) // 3
.with(mapper.schemaFor(AccessRecord.class).withHeader()) // 4
.readValues(input)) { // 5
List<AccessRecord> accesos = iterator.readAll(); // 6
System.out.println(accesos);
}
-
Input, en este caso es un string, puede ser un fichero, un InputStream, un Reader, etc.
-
Configuramos el mapper exactamente igual que el de lectura.
-
Indicamos el objeto destino.
-
Indicamos que nuestra entrada usa headers.
-
Leemos los datos y los guardamos en un MapIterator, usamos var para minimizar, se trata de un recurso por lo que ha de ser cerrado y por ello está en un tryWithResources.
-
Convertimos el iterador en una lista de objetos.
Opciones adicionales
Este post está centrado a poder hacer un CTRL+C y CTRL+V del código superior para poder rápidamente exportar a CSV con las características por defecto descritas. Si tienes necesidades adicionales, la librería es muy completa para ajustarse a todos los casos de uso, puedes revisarla en su página de github