toCSV()Salut à tous, je profite d’un peu de répit côté travail pour écrire un petit tutoriel mettant a profit des éléments de Java que l’on on a pas forcément l’occasion d’utiliser dans les projets d’entreprise :

  • Les annotations,
  • La réflection.

En effet, l’utilisation de ces éléments fondamentaux de Java est courante lors de la création d’une librairie, mais peu utilisée lorsqu’on participe à un projet d’application pour une entreprise.

Je vais ici m’intéresser à une fonctionnalité classique d’une application, l’export de tableaux en CSV.

[notice]On peut trouver pletor d’API Java qui font l’import ou l’export de fichiers CSV dont l’excellente OpenCSV, ou encore JavaCSV.Il faudra cependant se poser la question : Mon projet a-t-il vraiment besoin de s’appuyer sur une API, donc sur une dépendance externe, pour réaliser ma fonctionnalité, ou  puis-je facilement ajouter ce besoin à mon projet en développant 2 ou 3 classes supplémentaires?[/notice]

Dans mon cas, je me suis posé la question dans le cadre d’un projet pour un grand compte, et j’ai simplement fini par ajouter quelques classes pour faire ce dont j’avais besoin.
Vous êtes dans ce cas? Alors rentrons dans le détail!
[hr]

1. Annotate anywhere

Premier besoin somme toute classique, je veux que n’importe quel objet soit exportable en CSV, et que je puisse choisir les champs exportables. Pour cela, rien de mieux que les annotations. C’est clair, lisible, fonctionnel.

Pour déclarer un membre de classe exportable en CSV, j’aimerais donc simplement dire:

@CSVColumnHeader(name = "Name")
private String name;

Pour faire ça, c’est assez simple, nous allons rajouter une annotation qui pourra être utilisée n’importe où:


package fr.dijit.csv;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CSVColumnHeader {

public String name();

}

Attention au :

@Retention(RetentionPolicy.RUNTIME)

qui est essentiel pour pouvoir utiliser l’annotation dans votre EDI (éditeur de code source), et pour que l’annotation soit également visible à l’exécution du code (RUNTIME) ET ACCESSIBLE PAR L’API DE REFLEXION (java.lang.reflect).

2. Utilisation de la reflection pour exporter nos champs en CSV

Il faut maintenant exploiter ces annotations, c’est à dire repérer quand un objet déclare cette annotation, et faire ce qu’il faut lorsque l’on demande d’exporter en CSV.
Supposons que je souhaite exporter une liste d’objets en un fichier CSV… Je n’ai qu’a hériter d’une ArrayList et ajouter une petite fonction qu’on appelera toCSV() pour faire dans l’exubérance!

package fr.dijit.csv;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class CSVList<E> extends ArrayList<E> implements ICSVList {

	private static final long serialVersionUID = 1L;

	public String toCSV() throws IllegalArgumentException, IllegalAccessException {

		List<Field> csvFields = new ArrayList<Field>();
		StringBuilder csv = new StringBuilder();

		for (int i = 0; i < this.size(); i++) {

			E element = this.get(i);

			if (i == 0) {
				Field[] fields = element.getClass().getDeclaredFields();

				for (Field field : fields) {
					CSVColumnHeader csvColumnHeader = field.getAnnotation(CSVColumnHeader.class);
					if (csvColumnHeader == null) {
						continue;
					}
					csv.append(csvColumnHeader.name()).append(CSV_SEPARATOR);
					csvFields.add(field);
				}
				csv.append(CSV_LR);
			}

			for (Field field : csvFields) {
				field.setAccessible(true);
				csv.append(field.get(element)).append(CSV_SEPARATOR);
			}
			csv.append(CSV_LR);
		}
		return csv.toString();
	}
}

Je récupère les membres de mon premier élément de liste grâce à :

Field[] fields = element.getClass().getDeclaredFields()

et je pour chaque membre, je regarde s’il est annoté :

CSVColumnHeader csvColumnHeader = field.getAnnotation(CSVColumnHeader.class);

Si c’est le cas, j’ajoute au CSV les titres de colonne de mon tableau, et les valeurs pour chaque élément de ma liste avec :

csv.append(field.get(element)).append(CSV_SEPARATOR);

[attention]Ne pas oublier de rendre le champ accessible par

field.setAccessible(true);

car sinon aurez une belle exception due au fait que la classe CSVList ne pas pas accéder au membre privé de votre objet à exporter.
[/attention]

3. Utiliser notre petite API

Ça y est! En ajoutant 3 classes, je peux créer une liste d’objets, exportables en CSV:
Testons avec « una pequenio POJO » 😉 :

package fr.dijit.csv;

public class Person {

	@CSVColumnHeader(name = "Name")
	private String name;

	@CSVColumnHeader(name = "Age")
	private Integer age;

	private String address;

	public Person() {
	}

	public String getAddress() {
		return address;
	}

	public Integer getAge() {
		return age;
	}

	public String getName() {
		return name;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public void setName(String name) {
		this.name = name;
	}

}

Et une classe de test JUnit:

	@Test
	public void testFilledCSVListDoesNotGiveEmptyCSV() throws IllegalArgumentException, IllegalAccessException {

		CSVList persons = new CSVList();

		for (int i = 0; i < 10; i++) {
			Person person = new Person();
			person.setName("Number" + i);
			person.setAge(i);
			persons.add(person);
		}

		System.out.println(persons.toCSV());
		Assert.assertNotNull(persons.toCSV());
		Assert.assertTrue(!persons.toCSV().equals(""));
	}

Et nous obtenons:

Name;Age;
Number0;0;
Number1;1;
Number2;2;
Number3;3;
Number4;4;
Number5;5;
Number6;6;
Number7;7;
Number8;8;
Number9;9;

Pratique et pas cher… Je joins à cet article le code source pour exporter une liste d’objet en CSV, et bien sûr vous pouvez améliorer, agrémenter, transformer, modifier… je m’égare… à votre sauce!

3 thoughts to “Export CSV en Java: Tutorial utilisant la Reflection et les Annotations

  • Alex

    Merci pour l’article.
    Jolie démo de ce qu’on peut faire simplement avec Java 😉

    Répondre
    • Bon

      Lol simplement .. 3 classe 12 méthodes pour exporter 3 ligne le tuto est exellent mais ce language en demande trop pour simplement 2 ligne en résultat
      je sais pas pour vous mais je le trouve très frustrant ce java

      Répondre
      • dijit

        C’est vrai qu’avec Java rien n’est jamais « simple », comme le langage est assez blindé pour éviter au développeur de faire des erreurs, le code devient assez vite lourd.
        Du coup avec Java on écrit plus de code qu’avec d’autres langages. Si aujourd’hui j’avais à me mettre à quelque chose de plus simple, j’irais vers du Javascript, Python ou encore Go.
        Merci pour le commentaire en tout cas!

        Répondre

Leave a comment

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *