【问题标题】:OpenCSV - How to map selected columns to Java Bean regardless of order?OpenCSV - 如何将选定的列映射到 Java Bean 而不管顺序如何?
【发布时间】:2012-11-22 03:54:13
【问题描述】:

我有一个包含以下列的 CSV 文件:idfnametelephonelnameaddress

我有一个Person 类,其中包含idfnamelname 数据成员。我只想将这些列映射到 CSV 文件中的 Person 对象并丢弃 telephoneaddress 列。我怎样才能做到这一点?随着将来添加更多列,该解决方案必须扩展。并且无论列位置如何都应该工作。

在理想的解决方案中,用户只会指定要读取的列,它应该可以正常工作。

【问题讨论】:

    标签: java csv opencsv supercsv


    【解决方案1】:

    您可以使用HeaderColumnNameTranslateMappingStrategy。为简单起见,假设您的 CSV 具有以下列:IdFnameTelephoneLnameAddress

    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 对象映射列。

    【讨论】:

    • 是否可以将 csv "2015-01-02 23:59:50" 中的字符串映射到 bean 中的 joda DateTime 对象?
    • 这个方法已经过时了,所以我写了新的答案来展示如何使用 BeanBuilder stackoverflow.com/a/48227474/1238944
    【解决方案2】:

    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;
    

    【讨论】:

    • beanBuilder.build() 行获取java.lang.NoSuchMethodError: org.apache.commons.beanutils.ConvertUtilsBean.register(ZZI)V。我哪里错了?
    • 如何忽略未映射的 CSV 列?我的意思是,如果在 CSV 中有 10 列并且我只想读取其中的 5 个(这 5 个在 bean 中),那么呢?目前它正在抛出异常Number of data fields does not match number of headers.
    【解决方案3】:

    我不能代表 opencsv,但这很容易使用 Super CSV 实现,它有两个不同的 readers 支持 partial reading(忽略列),以及读入 Javabean。 CsvDozerBeanReader 甚至可以支持deep and index-based mapping,因此您可以映射到嵌套字段。

    我们(Super CSV 团队)刚刚发布了 2.0.0 版,可从 Maven 中心或 SourceForge 获得。

    更新

    这是一个示例(基于您创建的 GitHub 项目中的测试),它使用 Super CSV 而不是 opencsv。请注意,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();
    }
    

    【讨论】:

      【解决方案4】:

      使用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 许可)。

      【讨论】:

      • 如果我们甚至不需要空列怎么办。我们只想删除不需要的列?单义性有可能吗?
      • 使用settings.selectFieldssettings.selectIndexes 选择要解析的列。
      【解决方案5】:

      这是使用 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);
      

      【讨论】:

        【解决方案6】:

        在不需要写入csv文件的字段上方使用@CsvIgnore注解。

        【讨论】:

          【解决方案7】:

          使用 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中提供

          【讨论】:

            【解决方案8】:

            看看 jcsvdao,https://github.com/eric-mckinley/jcsvdao/,使用 hibernate 风格的映射文件,可以处理 1to1 和 1toMany 关系。 如果你不拥有 csv 文件,那很好,因为它有灵活的匹配策略。

            【讨论】:

            • 欢迎来到 SO。如果您在此处包含一些代码,而不是在链接后面,这将是一个更好的答案。
            【解决方案9】:

            jcvsdao 示例用法

            用户 CSV 文件示例

            Username, Email, Registration Date, Age, Premium User
            Jimmy, jim@test.com, 04-05-2016, 15, Yes, M
            Bob, bob@test.com, 15-01-2012, 32, No, M
            Alice, alice@test.com, 22-09-2011, 24, No, F
            Mike, mike@test.com, 11-03-2012, 18, Yes, M
            Helen, helen@test.com, 02-12-2013, 22, Yes, F
            Tom, tom@test.com, 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>
            

            【讨论】:

            • 可以用 Apache MetaModel 做同样的事情
            【解决方案10】:

            SimpleFlatMapper 可以轻松做到这一点 使用 csv 的标题或手动指定哪些列映射到哪些属性,请参阅Getting Started csv

            CsvParser
                .mapTo(MyObject.class)
                .forEach(file, System.out::println);
            

            【讨论】:

              猜你喜欢
              • 2020-04-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-27
              • 2017-07-24
              • 1970-01-01
              • 2016-04-06
              相关资源
              最近更新 更多