J'ai créé une classe MappingsBean dans laquelle toutes les colonnes du fichier CSV sont spécifiées. Ensuite, j'analyse des fichiers XML et crée une liste de mappingbeans. Ensuite, j'écris ces données dans un fichier CSV en tant que rapport.
J'utilise les annotations suivantes:
public class MappingsBean {
@CsvBindByName(column = "TradeID")
@CsvBindByPosition(position = 0)
private String tradeId;
@CsvBindByName(column = "GWML GUID", required = true)
@CsvBindByPosition(position = 1)
private String gwmlGUID;
@CsvBindByName(column = "MXML GUID", required = true)
@CsvBindByPosition(position = 2)
private String mxmlGUID;
@CsvBindByName(column = "GWML File")
@CsvBindByPosition(position = 3)
private String gwmlFile;
@CsvBindByName(column = "MxML File")
@CsvBindByPosition(position = 4)
private String mxmlFile;
@CsvBindByName(column = "MxML Counterparty")
@CsvBindByPosition(position = 5)
private String mxmlCounterParty;
@CsvBindByName(column = "GWML Counterparty")
@CsvBindByPosition(position = 6)
private String gwmlCounterParty;
}
Et puis j'utilise la classe StatefulBeanToCsv
pour écrire dans un fichier CSV:
File reportFile = new File(reportOutputDir + "/" + REPORT_FILENAME);
Writer writer = new PrintWriter(reportFile);
StatefulBeanToCsv<MappingsBean> beanToCsv = new
StatefulBeanToCsvBuilder(writer).build();
beanToCsv.write(makeFinalMappingBeanList());
writer.close();
Le problème avec cette approche est que si j'utilise @CsvBindByPosition(position = 0)
pour contrôler la position , Je ne peux pas générer de noms de colonnes. Si j'utilise @CsvBindByName(column = "TradeID")
, je ne peux pas définir la position des colonnes.
Existe-t-il un moyen d'utiliser les deux annotations pour créer des fichiers CSV avec des en-têtes de colonne et contrôler la position de la colonne?
Cordialement, Vikram Pathania
J'ai eu le même problème. Autant que je sache, OpenCSV ne contient aucune fonctionnalité permettant d’écrire un bean au format CSV avec des noms de colonne personnalisés et ordering.
Il existe deux principaux MappingStrategy
ies disponibles dans OpenCSV:
HeaderColumnNameMappingStrategy
: cela permet de mapper les colonnes de fichier CVS à des champs de haricots basés sur un nom personnalisé; lors de l'écriture de bean au format CSV, cela permet de changer le nom de l'en-tête de la colonne mais nous n'avons aucun contrôle sur l'ordre des colonnesColumnPositionMappingStrategy
: permet de mapper les colonnes de fichiers CSV aux champs de beans en fonction de l'ordre des colonnes; lors de l'écriture de bean au format CSV, nous pouvons contrôler l'ordre des colonnes, mais nous obtenons un en-tête vide (l'implémentation renvoie new String[0]
en tant qu'en-tête)Le seul moyen que j’ai trouvé d’obtenir des noms de colonne et un ordre personnalisés est d’écrire votre MappingStrategy
personnalisé.
Créer une MappingStrategy
personnalisée:
class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
private static final String[] HEADER = new String[]{"TradeID", "GWML GUID", "MXML GUID", "GWML File", "MxML File", "MxML Counterparty", "GWML Counterparty"};
@Override
public String[] generateHeader() {
return HEADER;
}
}
Et utilisez-le dans StatefulBeanToCsvBuilder
:
final CustomMappingStrategy<MappingsBean> mappingStrategy = new CustomMappingStrategy<>();
mappingStrategy.setType(MappingsBean.class);
final StatefulBeanToCsv<MappingsBean> beanToCsv = new StatefulBeanToCsvBuilder<MappingsBean>(writer)
.withMappingStrategy(mappingStrategy)
.build();
beanToCsv.write(makeFinalMappingBeanList());
writer.close()
Dans la classe MappingsBean
, nous avons laissé des annotations CsvBindByPosition
- pour contrôler le classement (dans cette solution, les annotations CsvBindByName
ne sont pas nécessaires). Grâce à la stratégie de mappage personnalisé, les noms des colonnes d'en-tête sont inclus dans le fichier CSV résultant.
L'inconvénient de cette solution est que, lorsque nous modifions l'ordre des colonnes via l'annotation CsvBindByPosition
, nous devons également modifier manuellement la constante HEADER
dans notre stratégie de mappage personnalisé.
La première solution fonctionne, mais ce n'était pas bon pour moi. Basé sur les implémentations intégrées de MappingStrategy
, je suis venu avec une autre implémentation:
class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
@Override
public String[] generateHeader() {
final int numColumns = findMaxFieldIndex();
if (!isAnnotationDriven() || numColumns == -1) {
return super.generateHeader();
}
header = new String[numColumns + 1];
BeanField beanField;
for (int i = 0; i <= numColumns; i++) {
beanField = findField(i);
String columnHeaderName = extractHeaderName(beanField);
header[i] = columnHeaderName;
}
return header;
}
private String extractHeaderName(final BeanField beanField) {
if (beanField == null || beanField.getField() == null || beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class).length == 0) {
return StringUtils.EMPTY;
}
final CsvBindByName bindByNameAnnotation = beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class)[0];
return bindByNameAnnotation.column();
}
}
Vous pouvez utiliser cette stratégie personnalisée dans StatefulBeanToCsvBuilder
exactement de la même façon que dans la première solution (n'oubliez pas d'appeler mappingStrategy.setType(MappingsBean.class);
, sinon cette solution ne fonctionnera pas).
Actuellement, notre MappingsBean
doit contenir à la fois les annotations CsvBindByName
et CsvBindByPosition
. Le premier à donner le nom de la colonne d'en-tête et le second à créer un ordre des colonnes dans l'en-tête CSV de sortie. Maintenant, si nous changeons (en utilisant des annotations) le nom de la colonne ou l'ordre dans la classe MappingsBean
- cette modification sera reflétée dans le fichier CSV de sortie.
Correction de la réponse ci-dessus pour correspondre à la nouvelle version.
package csvpojo;
import org.Apache.commons.lang3.StringUtils;
import com.opencsv.bean.BeanField;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.CsvBindByName;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
@Override
public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
super.setColumnMapping(new String[ FieldUtils.getAllFields(bean.getClass()).length]);
final int numColumns = findMaxFieldIndex();
if (!isAnnotationDriven() || numColumns == -1) {
return super.generateHeader(bean);
}
String[] header = new String[numColumns + 1];
BeanField<T> beanField;
for (int i = 0; i <= numColumns; i++) {
beanField = findField(i);
String columnHeaderName = extractHeaderName(beanField);
header[i] = columnHeaderName;
}
return header;
}
private String extractHeaderName(final BeanField<T> beanField) {
if (beanField == null || beanField.getField() == null
|| beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class).length == 0) {
return StringUtils.EMPTY;
}
final CsvBindByName bindByNameAnnotation = beanField.getField()
.getDeclaredAnnotationsByType(CsvBindByName.class)[0];
return bindByNameAnnotation.column();
}
}
Appelez ensuite ceci pour générer du CSV. J'ai utilisé les visiteurs comme mon POJO pour peupler, mettre à jour si nécessaire.
CustomMappingStrategy<Visitors> mappingStrategy = new CustomMappingStrategy<>();
mappingStrategy.setType(Visitors.class);
// writing sample
List<Visitors> beans2 = new ArrayList<Visitors>();
Visitors v = new Visitors();
v.set_1_firstName(" test1");
v.set_2_lastName("lastname1");
v.set_3_visitsToWebsite("876");
beans2.add(v);
v = new Visitors();
v.set_1_firstName(" firstsample2");
v.set_2_lastName("lastname2");
v.set_3_visitsToWebsite("777");
beans2.add(v);
Writer writer = new FileWriter("G://output.csv");
StatefulBeanToCsv<Visitors> beanToCsv = new StatefulBeanToCsvBuilder<Visitors>(writer)
.withMappingStrategy(mappingStrategy).withSeparator(',').withApplyQuotesToAll(false).build();
beanToCsv.write(beans2);
writer.close();
Mes annotations de haricots ressemblent à ceci
@CsvBindByName (column = "First Name", required = true)
@CsvBindByPosition(position=1)
private String firstName;
@CsvBindByName (column = "Last Name", required = true)
@CsvBindByPosition(position=0)
private String lastName;
merci pour ce fil, cela m’a été vraiment utile ... j’ai un peu amélioré la solution fournie afin d’accepter aussi POJO où certains champs ne sont pas annotés (ne sont pas destinés à être lus/écrits):
public class ColumnAndNameMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
@Override
public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
super.setColumnMapping(new String[ getAnnotatedFields(bean)]);
final int numColumns = getAnnotatedFields(bean);
final int totalFieldNum = findMaxFieldIndex();
if (!isAnnotationDriven() || numColumns == -1) {
return super.generateHeader(bean);
}
String[] header = new String[numColumns];
BeanField<T> beanField;
for (int i = 0; i <= totalFieldNum; i++) {
beanField = findField(i);
if (isFieldAnnotated(beanField.getField())) {
String columnHeaderName = extractHeaderName(beanField);
header[i] = columnHeaderName;
}
}
return header;
}
private int getAnnotatedFields(T bean) {
return (int) Arrays.stream(FieldUtils.getAllFields(bean.getClass()))
.filter(this::isFieldAnnotated)
.count();
}
private boolean isFieldAnnotated(Field f) {
return f.isAnnotationPresent(CsvBindByName.class) || f.isAnnotationPresent(CsvCustomBindByName.class);
}
private String extractHeaderName(final BeanField beanField) {
if (beanField == null || beanField.getField() == null) {
return StringUtils.EMPTY;
}
Field field = beanField.getField();
if (field.getDeclaredAnnotationsByType(CsvBindByName.class).length != 0) {
final CsvBindByName bindByNameAnnotation = field.getDeclaredAnnotationsByType(CsvBindByName.class)[0];
return bindByNameAnnotation.column();
}
if (field.getDeclaredAnnotationsByType(CsvCustomBindByName.class).length != 0) {
final CsvCustomBindByName bindByNameAnnotation = field.getDeclaredAnnotationsByType(CsvCustomBindByName.class)[0];
return bindByNameAnnotation.column();
}
return StringUtils.EMPTY;
}
}
Essayez quelque chose comme ci-dessous:
private static class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
String[] header;
public CustomMappingStrategy(String[] cols) {
header = cols;
}
@Override
public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
return header;
}
}
Puis utilisez-le comme suit:
String[] columns = new String[]{"Name", "Age", "Company", "Salary"};
CustomMappingStrategy<Employee> mappingStrategy = new CustomMappingStrategy<Employee>(columns);
Où les colonnes sont les colonnes de votre haricot et Employee est votre haricot
Si vous n'avez pas de méthode getDeclaredAnnotationsByType, mais avez besoin du nom de votre nom de champ d'origine:
beanField.getField (). getName ()
public class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
@Override
public String[] generateHeader() {
final int numColumns = findMaxFieldIndex();
if (!isAnnotationDriven() || numColumns == -1) {
return super.generateHeader();
}
header = new String[numColumns + 1];
BeanField beanField;
for (int i = 0; i <= numColumns; i++) {
beanField = findField(i);
String columnHeaderName = extractHeaderName(beanField);
header[i] = columnHeaderName;
}
return header;
}
private String extractHeaderName(final BeanField beanField) {
if (beanField == null || beanField.getField() == null || beanField.getField().getDeclaredAnnotations().length == 0) {
return StringUtils.EMPTY;
}
return beanField.getField().getName();
}
}
Je souhaitais obtenir les mêmes résultats que le sujet, mais je voulais aussi pouvoir importer le fichier CSV généré dans POJO. Aucune solution présentée ne m'a aidé à y parvenir.
Pour obtenir mes résultats, je devais refuser d'utiliser @CsvBindByPosition, car dans ce cas, ColumnPositionMappingStrategy était sélectionné automatiquement. Par documents: cette stratégie nécessite que le fichier ne comporte PAS d'en-tête .
Mais si nous écrivons avec openCSV avec une liaison par nom de colonne, un en-tête est ajouté automatiquement (nous pouvons le supprimer, mais je le trouve utile).
HeaderColumnNameMappingStrategy
mappingStrategy.setColumnOrderOnWrite(Comparator<String> writeOrder)
CsvUtils pour lire/écrire en csv
import com.opencsv.CSVWriter;
import com.opencsv.bean.*;
import org.springframework.web.multipart.MultipartFile;
import Java.io.*;
import Java.util.List;
public class CsvUtils {
private CsvUtils() {
}
public static <T> String convertToCsv(List<T> entitiesList, MappingStrategy<T> mappingStrategy) throws Exception {
try (Writer writer = new StringWriter()) {
StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(writer)
.withMappingStrategy(mappingStrategy)
.withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
.build();
beanToCsv.write(entitiesList);
return writer.toString();
}
}
@SuppressWarnings("unchecked")
public static <T> List<T> convertFromCsv(MultipartFile file, Class clazz) throws IOException {
try (Reader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {
CsvToBean<T> csvToBean = new CsvToBeanBuilder<T>(reader).withType(clazz).build();
return csvToBean.parse();
}
}
}
POJO pour import/export
public class LocalBusinessTrainingPairDTO {
//this is used for CSV columns ordering on exporting LocalBusinessTrainingPairs
public static final String[] FIELDS_ORDER = {"leftId", "leftName", "rightId", "rightName"};
@CsvBindByName(column = "leftId")
private int leftId;
@CsvBindByName(column = "leftName")
private String leftName;
@CsvBindByName(column = "rightId")
private int rightId;
@CsvBindByName(column = "rightName")
private String rightName;
// getters/setters omitted, do not forget to add them
}
Comparateur personnalisé pour un ordre de chaîne prédéfini:
public class OrderedComparatorIgnoringCase implements Comparator<String> {
private List<String> predefinedOrder;
public OrderedComparatorIgnoringCase(String[] predefinedOrder) {
this.predefinedOrder = new ArrayList<>();
for (String item : predefinedOrder) {
this.predefinedOrder.add(item.toLowerCase());
}
}
@Override
public int compare(String o1, String o2) {
return predefinedOrder.indexOf(o1.toLowerCase()) - predefinedOrder.indexOf(o2.toLowerCase());
}
}
Commande écrite pour POJO (réponse à la question initiale)
public static void main(String[] args) throws Exception {
List<LocalBusinessTrainingPairDTO> localBusinessTrainingPairsDTO = new ArrayList<>();
LocalBusinessTrainingPairDTO localBusinessTrainingPairDTO = new LocalBusinessTrainingPairDTO();
localBusinessTrainingPairDTO.setLeftId(1);
localBusinessTrainingPairDTO.setLeftName("leftName");
localBusinessTrainingPairDTO.setRightId(2);
localBusinessTrainingPairDTO.setRightName("rightName");
localBusinessTrainingPairsDTO.add(localBusinessTrainingPairDTO);
//Creating HeaderColumnNameMappingStrategy
HeaderColumnNameMappingStrategy<LocalBusinessTrainingPairDTO> mappingStrategy = new HeaderColumnNameMappingStrategy<>();
mappingStrategy.setType(LocalBusinessTrainingPairDTO.class);
//Setting predefined order using String comparator
mappingStrategy.setColumnOrderOnWrite(new OrderedComparatorIgnoringCase(LocalBusinessTrainingPairDTO.FIELDS_ORDER));
String csv = convertToCsv(localBusinessTrainingPairsDTO, mappingStrategy);
System.out.println(csv);
}
Lire le fichier CSV exporté dans POJO (en plus de la réponse d'origine)
Important: le format CSV peut être désordonné, car nous utilisons toujours la liaison par nom:
public static void main(String[] args) throws Exception {
//omitted code from writing
String csv = convertToCsv(localBusinessTrainingPairsDTO, mappingStrategy);
//Exported CSV should be compatible for further import
File temp = File.createTempFile("tempTrainingPairs", ".csv");
temp.deleteOnExit();
BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
bw.write(csv);
bw.close();
MultipartFile multipartFile = new MockMultipartFile("tempTrainingPairs.csv", new FileInputStream(temp));
List<LocalBusinessTrainingPairDTO> localBusinessTrainingPairDTOList = convertFromCsv(multipartFile, LocalBusinessTrainingPairDTO.class);
}