OpenCSV: How to create CSV file from POJO with custom column headers and custom column positions?

后端 未结 19 1452
温柔的废话
温柔的废话 2020-12-08 04:14

I have created a MappingsBean class where all the columns of the CSV file are specified. Next I parse XML files and create a list of mappingbeans. Then I write that data int

相关标签:
19条回答
  • 2020-12-08 04:46

    thanks for this thread, it has been really useful for me... I've enhanced a little bit the provided solution in order to accept also POJO where some fields are not annotated (not meant to be read/written):

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

    }

    0 讨论(0)
  • 2020-12-08 04:47

    The sebast26's first solution worked for me but for opencsv version 5.2 it requires a little change in the CustomMappingStrategy class:

    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() {
        super.generateHeader(bean); // without this the file contains ONLY headers
        return HEADER;
    }
    

    }

    0 讨论(0)
  • 2020-12-08 04:51

    This can be done using a HeaderColumnNameMappingStrategy along with a custom Comparator as well. Which is recommended by the official doc http://opencsv.sourceforge.net/#mapping_strategies

        File reportFile = new File(reportOutputDir + "/" + REPORT_FILENAME);
        Writer writer = new PrintWriter(reportFile);
        
    final List<String> order = List.of("TradeID", "GWML GUID", "MXML GUID", "GWML File", "MxML File", "MxML Counterparty", "GWML Counterparty");
        final FixedOrderComparator comparator = new FixedOrderComparator(order);
        HeaderColumnNameMappingStrategy<MappingsBean> strategy = new HeaderColumnNameMappingStrategy<>();
        strategy.setType(MappingsBean.class);
        strategy.setColumnOrderOnWrite(comparator);
    
        StatefulBeanToCsv<MappingsBean> beanToCsv = new
          StatefulBeanToCsvBuilder(writer)
          .withMappingStrategy(strategy)
          .build();
        beanToCsv.write(makeFinalMappingBeanList());
        writer.close();
    
    0 讨论(0)
  • 2020-12-08 04:52

    I think the intended and most flexible way of handling the order of the header columns is to inject a comparator by HeaderColumnNameMappinStrategy.setColumnOrderOnWrite().

    For me the most intuitive way was to write the CSV columns in the same order as they are specified in the CsvBean, but you can also adjust the Comparator to make use of your own annotations where you specify the order. Don´t forget to rename the Comparator class then ;)

    Integration:

    HeaderColumnNameMappingStrategy<MyCsvBean> mappingStrategy = new HeaderColumnNameMappingStrategy<>();
        mappingStrategy.setType(MyCsvBean.class);
        mappingStrategy.setColumnOrderOnWrite(new ClassFieldOrderComparator(MyCsvBean.class));
    

    Comparator:

    private class ClassFieldOrderComparator implements Comparator<String> {
    
        List<String> fieldNamesInOrderWithinClass;
    
        public ClassFieldOrderComparator(Class<?> clazz) {
            fieldNamesInOrderWithinClass = Arrays.stream(clazz.getDeclaredFields())
                    .filter(field -> field.getAnnotation(CsvBindByName.class) != null)
                  // Handle order by your custom annotation here
                  //.sorted((field1, field2) -> {
                  //   int field1Order = field1.getAnnotation(YourCustomOrderAnnotation.class).getOrder();
                  //   int field2Order = field2.getAnnotation(YourCustomOrderAnnotation.class).getOrder();
                  //   return Integer.compare(field1Order, field2Order);
                  //})
                    .map(field -> field.getName().toUpperCase())
                    .collect(Collectors.toList());
        }
    
        @Override
        public int compare(String o1, String o2) {
            int fieldIndexo1 = fieldNamesInOrderWithinClass.indexOf(o1);
            int fieldIndexo2 = fieldNamesInOrderWithinClass.indexOf(o2);
            return Integer.compare(fieldIndexo1, fieldIndexo2);
        }
    }
    
    0 讨论(0)
  • 2020-12-08 04:53

    Corrected above answer to match with newer 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();
        }
    }
    

    Then call this to generate CSV. I have used Visitors as my POJO to populate, update wherever necessary.

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

    My bean annotations looks like this

     @CsvBindByName (column = "First Name", required = true)
     @CsvBindByPosition(position=1)
     private String firstName;
    
    
     @CsvBindByName (column = "Last Name", required = true)
     @CsvBindByPosition(position=0)
     private String lastName;
    
    0 讨论(0)
  • 2020-12-08 04:55

    It is a solution for version greater than 4.3:

    public class MappingBean {
         @CsvBindByName(column = "column_a")
         private String columnA;
         @CsvBindByName(column = "column_b")
         private String columnB;
         @CsvBindByName(column = "column_c")
         private String columnC;
    
         // getters and setters
    }
    

    And use it as example:

    import org.apache.commons.collections4.comparators.FixedOrderComparator;
    

    ...

    var mappingStrategy = new HeaderColumnNameMappingStrategy<MappingBean>();
    mappingStrategy.setType(MappingBean.class);        
    mappingStrategy.setColumnOrderOnWrite(new FixedOrderComparator<>("COLUMN_C", "COLUMN_B", "COLUMN_A"));
    
    var sbc = new StatefulBeanToCsvBuilder<MappingBean>(writer)
          .withMappingStrategy(mappingStrategy)
          .build();
    

    Result:

    column_c | column_b | column_a

    0 讨论(0)
提交回复
热议问题