J'ai un fichier CSV avec les colonnes suivantes: id
, fname
, telephone
, lname
, address
.
J'ai une classe Person
avec des membres de données id
, fname
et lname
. Je souhaite mapper uniquement ces colonnes sur l'objet Person
à partir d'un fichier CSV et ignorer les colonnes telephone
et address
. Comment puis-je faire ceci? La solution doit évoluer à mesure que d'autres colonnes sont ajoutées à l'avenir. Et devrait fonctionner indépendamment de la position de la colonne.
Dans une solution idéale, l’utilisateur ne spécifiera que les colonnes à lire et cela devrait fonctionner.
Vous pouvez utiliser HeaderColumnNameTranslateMappingStrategy . Supposons que votre CSV comporte les colonnes suivantes: Id
, Fname
, Telephone
, Lname
, Address
par souci de simplicité.
CsvToBean<Person> csvToBean = new CsvToBean<Person>();
Map<String, String> columnMapping = new HashMap<String, String>();
columnMapping.put("Id", "id");
columnMapping.put("Fname", "fname");
columnMapping.put("Lname", "lname");
HeaderColumnNameTranslateMappingStrategy<Person> strategy = new HeaderColumnNameTranslateMappingStrategy<Person>();
strategy.setType(Person.class);
strategy.setColumnMapping(columnMapping);
List<Person> list = null;
CSVReader reader = new CSVReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("test.csv")));
list = csvToBean.parse(strategy, reader);
ColumnMapping mappera les colonnes avec votre objet Person
.
Les versions récentes d'OpenCSV déconseillent la méthode parse(X, Y)
et il est recommandé d'utiliser BeanBuilder à la place, de sorte que la première réponse est obsolète.
try {
CsvToBeanBuilder<PersonCSV> beanBuilder = new CsvToBeanBuilder<>(new InputStreamReader(new FileInputStream("your.csv")));
beanBuilder.withType(PersonCSV.class);
// build methods returns a list of Beans
beanBuilder.build().parse().forEach(e -> log.error(e.toString()));
} catch (FileNotFoundException e) {
log.error(e.getMessage(), e);
}
Cette méthode vous permet de nettoyer le code et de supprimer MappingStrategy (vous pouvez toujours l'utiliser si vous aimez les spaghettis) afin que vous puissiez annoter votre classe CSV comme suit:
@CsvDate("dd/MM/yyyy hh:mm:ss")
@CsvBindByName(column = "Time Born", required = true)
private Date birthDate;
Je ne peux pas parler pour opencsv, mais cela est facilement réalisable avec Super CSV , qui a deux lecteurs différents qui supportent lecture partielle (ignorant les colonnes), comme ainsi que la lecture dans un Javabéen. CsvDozerBeanReader
est même capable de cartographie profonde et basée sur un index }, afin que vous puissiez mapper des champs imbriqués.
Nous (l'équipe de Super CSV) venons de publier la version 2.0.0, disponible à partir de Maven central ou de SourceForge.
Mettre à jour
Voici un exemple (basé sur le test du projet GitHub que vous avez créé), qui utilise Super CSV au lieu d’opencsv. Notez que les préférences CSV exigent que l'indicateur surroundingSpacesNeedQuotes
soit activé car votre exemple de fichier CSV n'est pas valide (il comporte des espaces entre les champs - les espaces sont considérés comme faisant partie des données en CSV).
ICsvBeanReader beanReader = null;
try {
beanReader = new CsvBeanReader(
new InputStreamReader(
ClassLoader.getSystemResourceAsStream("test.csv")),
new CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE)
.surroundingSpacesNeedQuotes(true).build());
List<String> columnsToMap = Arrays.asList("fname", "telephone", "id");
// read the CSV header (and set any unwanted columns to null)
String[] header = beanReader.getHeader(true);
for (int i = 0; i < header.length; i++) {
if (!columnsToMap.contains(header[i])) {
header[i] = null;
}
}
Person person;
while ((person = beanReader.read(Person.class, header)) != null) {
System.out.println(person);
}
} finally {
beanReader.close();
}
Utilisez uniVocity-parsers et faites-le. Peu importe la manière dont les colonnes sont organisées dans le fichier CSV d'entrée, seules celles dont vous avez besoin seront analysées.
Si vous écrivez, les colonnes que vous avez en classe seront écrites dans les colonnes correctes, tandis que les autres seront vides.
Voici une classe avec quelques exemples:
class TestBean {
// if the value parsed in the quantity column is "?" or "-", it will be replaced by null.
@NullString(nulls = { "?", "-" })
// if a value resolves to null, it will be converted to the String "0".
@Parsed(defaultNullRead = "0")
private Integer quantity; // The attribute type defines which conversion will be executed when processing the value.
@Trim
@LowerCase
// the value for the comments attribute is in the column at index 4 (0 is the first column, so this means fifth column in the file)
@Parsed(index = 4)
private String comments;
// you can also explicitly give the name of a column in the file.
@Parsed(field = "amount")
private BigDecimal amount;
@Trim
@LowerCase
// values "no", "n" and "null" will be converted to false; values "yes" and "y" will be converted to true
@BooleanString(falseStrings = { "no", "n", "null" }, trueStrings = { "yes", "y" })
@Parsed
private Boolean pending;
}
Voici comment obtenir une liste de TestBean
BeanListProcessor<TestBean> rowProcessor = new BeanListProcessor<TestBean>(TestBean.class);
CsvParserSettings parserSettings = new CsvParserSettings();
parserSettings.setRowProcessor(rowProcessor);
parserSettings.setHeaderExtractionEnabled(true);
CsvParser parser = new CsvParser(parserSettings);
parser.parse(getReader("/examples/bean_test.csv"));
List<TestBean> beans = rowProcessor.getBeans();
Divulgation: Je suis l'auteur de cette bibliothèque. Il est open-source et gratuit (licence Apache V2.0).
Voici une bonne façon d'utiliser OpenCSV pour effectuer le mappage vers POJO de manière générique:
protected <T> List<T> mapToCSV(String csvContent, Class<T> mapToClass) {
CsvToBean<T> csvToBean = new CsvToBean<T>();
Map<String, String> columnMapping = new HashMap<>();
Arrays.stream(mapToClass.getDeclaredFields()).forEach(field -> {
columnMapping.put(field.getName(), field.getName());
});
HeaderColumnNameTranslateMappingStrategy<T> strategy = new HeaderColumnNameTranslateMappingStrategy<T>();
strategy.setType(mapToClass);
strategy.setColumnMapping(columnMapping);
CSVReader reader = new CSVReader(new StringReader(csvContent));
return csvToBean.parse(strategy, reader);
}
public static class MyPojo {
private String foo, bar;
public void setFoo(String foo) {
this.foo = foo;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Ensuite, à partir de votre test, vous pouvez utiliser:
List<MyPojo> list = mapToCSV(csvContent, MyPojo.class);
exemple d'utilisation de jcvsdao
Exemple de fichier CSV utilisateur
Username, Email, Registration Date, Age, Premium User
Jimmy, [email protected], 04-05-2016, 15, Yes, M
Bob, [email protected], 15-01-2012, 32, No, M
Alice, [email protected], 22-09-2011, 24, No, F
Mike, [email protected], 11-03-2012, 18, Yes, M
Helen, [email protected], 02-12-2013, 22, Yes, F
Tom, [email protected], 08-11-2015, 45, No, M
Créer un CsvDao
CSVDaoFactory factory = new CSVDaoFactory("/csv-config.xml");
CSVDao dao = new CSVDao(factory);
List<UserDetail> users = dao.find(UserDetail.class);
csv-config.xml
<CSVConfig>
<mappingFiles fileType="resource">
<mappingFile>/example01/mapping/UserDetail.csv.xml</mappingFile>
</mappingFiles>
</CSVConfig>
UserDetail.csv.xml
<CSVMapping className="org.jcsvdao.examples.example01.model.UserDetail" csvFile="csv-examples/example01/users.txt" delimiter="," ignoreFirstLine="true">
<matchAll/>
<properties>
<property index="0" property="username" primaryKey="true"/>
<property index="1" property="email"/>
<property index="2" property="registrationDate" converter="myDateConverter"/>
<property index="3" property="age"/>
<property index="4" property="premiumUser" converter="yesNoConverter"/>
<property index="5" property="gender" converter="myGenderConverter"/>
</properties>
<converters>
<dateConverter converterName="myDateConverter" format="dd-MM-yyyy"/>
<booleanConverter converterName="yesNoConverter" positive="Yes" negative="No"/>
<customConverter converterName="myGenderConverter" converterClass="org.jcsvdao.examples.example01.converter.GenderCustomerConverter"/>
</converters>
</CSVMapping>
la dernière version de https://github.com/arnaudroger/SimpleFlatMapper 0.9.4 a maintenant un CsvMapper. Il utilise l'en-tête pour faire correspondre le nom de la propriété ou, si aucun en-tête, vous pouvez spécifier le nom de la colonne via le générateur . Il prend en charge l'injection de constructeur, de définition et de champ. Lire depuis InputStream ou Reader.
public class MyParser {
private final CsvMapper<MyObject> mapper =
CsvMapperFactory.newInstance().newMapper(MyObject.class);
public void writeAllObjectToLambda(Writer writer, InputStream is) throws IOException {
mapper.forEach(is, (o) -> writer.append(o.toString()).append("\n"));
}
}
Jetez un oeil à jcsvdao, https://github.com/eric-mckinley/jcsvdao/ , utilise des fichiers de mappage de style hibernation et peut gérer des relations 1to1 et 1toMany Bon si vous ne possédez pas les fichiers csv car dispose de stratégies de concordance flexibles.
J'ai mis en place une solution flexible pour résoudre ce problème. C'est très simple à utiliser et le code avec exemple est disponible sur mon github ci-dessous: