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.

  1. El separador son comas.

  2. Se han de incluir cabeceras.

  3. El orden de las cabeceras ha de ser el mismo que el declarado en el POJO.

  4. Los valores de tipo String han sido escapados usando comillas.

  5. El separador de decimales es el punto.

  6. Las fechas han de estar en formato ISO8601.

  7. 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);
  1. Conjunto de datos a escribir.

  2. Creamos el mapper para escribir en CSV.

    1. Registramos los módulos extra, en este caso jsr310 para poder escribir tiempos.

    2. Indicamos que las fechas se han de visualizar en formato texto y no mo timestamps.

    3. Configuramos el mapper para que no ordene alfabeticamente los capos al escribir.

  3. Configuramos nuestro destino, en este caso un string en memoria pero puede ser un File, un OutputStream, etc.

  4. 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.

  5. Configuramos un writer indicando que queremos cabeceras.

  6. Establecemos donde vamos a querer escribir.

  7. 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);
}
  1. Input, en este caso es un string, puede ser un fichero, un InputStream, un Reader, etc.

  2. Configuramos el mapper exactamente igual que el de lectura.

  3. Indicamos el objeto destino.

  4. Indicamos que nuestra entrada usa headers.

  5. 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.

  6. 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