OpenCSV - Как сопоставить выбранные столбцы с Java Bean независимо от порядка?

У меня есть файл CSV со следующими столбцами: id, fname, telephone, lname, address.

У меня есть класс Person с элементами данных id, fname и lname. Я хочу сопоставить только эти столбцы с объектом Person из файла CSV и отбросить столбцы telephone и address. Как я могу это сделать? Решение должно масштабироваться по мере добавления новых столбцов в будущем. И должен работать независимо от положения столбца.

В идеальном решении пользователь будет указывать только столбцы для чтения, и оно должно работать.


person jsf    schedule 22.11.2012    source источник


Ответы (10)


Вы можете использовать HeaderColumnNameTranslateMappingStrategy. Предположим, что ваш CSV имеет следующие столбцы: Id, Fname, Telephone, Lname, Address для простоты.

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 сопоставит столбцы с вашим объектом Person.

person baskar_p    schedule 20.02.2013
comment
Можно ли сопоставить строку в csv "2015-01-02 23:59:50" с объектом joda DateTime внутри bean-компонента? - person root; 23.10.2015
comment
Этот метод устарел, поэтому я написал новый ответ, который показывает, как использовать BeanBuilder stackoverflow.com/a/48227474/1238944 - person agilob; 12.01.2018

В последних версиях OpenCSV метод parse(X, Y) не рекомендуется, и вместо него рекомендуется использовать BeanBuilder, поэтому главный ответ устарел.

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);
}

Эти методы позволяют очистить код и удалить MappingStrategy (вы все еще можете использовать его, если любите спагетти), поэтому вы можете аннотировать свой CSV-класс следующим образом:

@CsvDate("dd/MM/yyyy hh:mm:ss")
@CsvBindByName(column = "Time Born", required = true)
private Date birthDate;
person agilob    schedule 12.01.2018
comment
Получение java.lang.NoSuchMethodError: org.apache.commons.beanutils.ConvertUtilsBean.register(ZZI)V в строке beanBuilder.build(). Где я ошибаюсь? - person Shubham; 31.05.2021
comment
Как игнорировать несопоставленные столбцы CSV? Я имею в виду, если в CSV есть 10 столбцов, и я хочу прочитать только 5 (эти 5 есть в bean-компоненте) из них? В настоящее время выдается исключение Number of data fields does not match number of headers. - person Jignesh M. Khatri; 02.07.2021

Я не могу говорить за opencsv, но это легко достижимо с помощью Super CSV с двумя разными читателями, которые поддерживают частичное чтение (игнорирование столбцов), а также чтение в Javabean. CsvDozerBeanReader даже поддерживает глубокое сопоставление на основе индексов, так что вы можете сопоставлять вложенные поля.

Мы (команда Super CSV) только что выпустили версию 2.0.0, которая доступна в Maven Central или SourceForge.

Обновить

Вот пример (на основе теста в созданном вами проекте GitHub), в котором вместо opencsv используется Super CSV. Обратите внимание, что в настройках CSV необходимо включить флаг surroundingSpacesNeedQuotes, поскольку ваш пример CSV-файла недействителен (в нем есть пробелы между полями — пробелы считаются частью данных в 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();
}
person James Bassett    schedule 23.11.2012

Используйте uniVocity-parsers и покончите с этим. Неважно, как столбцы организованы во входном CSV, будут проанализированы только те, которые вам нужны.

При записи столбцы, которые у вас есть в классе, будут записаны в правильные столбцы, а остальные будут пустыми.

Вот класс с некоторыми примерами:

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;
}

Вот как получить список 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();

Раскрытие информации: я являюсь автором этой библиотеки. Это бесплатно и с открытым исходным кодом (лицензия Apache V2.0).

person Jeronimo Backes    schedule 05.05.2015
comment
Что, если нам даже не нужен пустой столбец. Мы просто хотим удалить ненужные столбцы? Возможно ли это с однозначностью? - person Piyush N; 19.02.2020
comment
используйте settings.selectFields или settings.selectIndexes, чтобы выбрать, какие столбцы анализировать. - person Jeronimo Backes; 20.02.2020

Вот хороший способ использовать OpenCSV для общего сопоставления с POJO:

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;
    }
}

Затем из вашего теста вы можете использовать:

List<MyPojo> list = mapToCSV(csvContent, MyPojo.class);
person Paul Hilliar    schedule 01.04.2016

Используйте аннотацию @CsvIgnore над полем, которое не нужно записывать в файл csv.

person Adarsh Mohan    schedule 11.11.2020

С opencsv вы можете создать общую функцию, например:

public static <T> void csvWriterUtil(Class<T> beanClass, List<T> data, String outputFile, String[] columnMapping) {
    try {
        Writer writer = new BufferedWriter(new FileWriter(outputFile));
        ColumnPositionMappingStrategy<T> strategy = new ColumnPositionMappingStrategy<>();
        strategy.setType(beanClass);
        strategy.setColumnMapping(columnMapping);
        StatefulBeanToCsv<T> statefulBeanToCsv =new StatefulBeanToCsvBuilder<T>(writer)
                .withMappingStrategy(strategy)
                .build();
        writer.write(String.join(",",columnMapping)+"\n");
        statefulBeanToCsv.write(data);
        writer.close();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (CsvRequiredFieldEmptyException e) {
        e.printStackTrace();
    } catch (CsvDataTypeMismatchException e) {
        e.printStackTrace();
    }
}

Здесь вы можете передать только необходимые столбцы через параметр columnMapping.

Пример кода доступен в https://github.com/soumya-kole/JavaUtils/tree/master/CsvUtil

person soumya-kole    schedule 24.09.2020

Взгляните на jcsvdao, https://github.com/eric-mckinley/jcsvdao/. , использует файлы сопоставления в стиле гибернации и может обрабатывать отношения 1to1 и 1toMany. Хорошо, если у вас нет CSV-файлов, поскольку у вас есть гибкие стратегии сопоставления.

person lex404    schedule 25.02.2016
comment
Добро пожаловать в СО. Это был бы гораздо лучший ответ, если бы вы включили здесь некоторый код, а не за ссылкой. - person Teepeemm; 26.02.2016

пример использования jcvsdao

Образец пользовательского CSV-файла

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

Создать 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>
person lex404    schedule 05.03.2016
comment
Можно сделать то же самое с Apache MetaModel - person djangofan; 04.03.2019

SimpleFlatMapper может легко сделать это, см. Начало работы csv с помощью заголовков csv или вручную указав, какие столбцы соответствуют какому свойству.

CsvParser
    .mapTo(MyObject.class)
    .forEach(file, System.out::println);
person user3996996    schedule 26.09.2014