programing

OpenCSV: 사용자 지정 컬럼 헤더 및 사용자 지정 컬럼 위치를 사용하여 POJO에서 CSV 파일을 생성하는 방법은?

padding 2023. 10. 30. 20:48
반응형

OpenCSV: 사용자 지정 컬럼 헤더 및 사용자 지정 컬럼 위치를 사용하여 POJO에서 CSV 파일을 생성하는 방법은?

CSV 파일의 모든 열이 지정된 MappingsBean 클래스를 만들었습니다.다음으로 XML 파일을 파싱하고 매핑 빈 목록을 만듭니다.그리고 그 자료를 보고서로 CSV 파일에 씁니다.

다음 주석을 사용하고 있습니다.

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

그다음에 저는.StatefulBeanToCsvCSV 파일에 쓸 클래스:

File reportFile = new File(reportOutputDir + "/" + REPORT_FILENAME);
Writer writer = new PrintWriter(reportFile);
StatefulBeanToCsv<MappingsBean> beanToCsv = new 
                              StatefulBeanToCsvBuilder(writer).build();
beanToCsv.write(makeFinalMappingBeanList());
writer.close();

이 접근법의 문제점은 만약 내가@CsvBindByPosition(position = 0)위치를 제어하려면 열 이름을 생성할 수 없습니다.사용하면@CsvBindByName(column = "TradeID")그러면 기둥의 위치를 설정할 수 없습니다.

컬럼 헤더로 CSV 파일을 생성하고 컬럼 위치도 제어할 수 있도록 두 주석을 모두 사용할 수 있는 방법이 있습니까?

안녕하세요, 비크람 파타니아

저도 비슷한 문제를 겪었습니다.AFAIK OpenCSV에는 사용자 지정 열 이름과 순서를 사용하여 CSV에 빈을 쓸 수 있는 내장 기능이 없습니다.

크게 두가지가 있습니다.MappingStrategyOpenCSV에서 즉시 사용 가능한 ies:

  • HeaderColumnNameMappingStrategy: 사용자 지정 이름을 기반으로 CVS 파일 열을 be 필드에 매핑할 수 있습니다. CSV에 bein을 쓸 때는 column 헤더 이름을 변경할 수 있지만 column order에 대해서는 제어할 수 없습니다.
  • ColumnPositionMappingStrategy열 수 쓸 때 열할 수 빈구현 : ). CSV에 빈을 쓸 때 열 순서는 제어할 수 있지만 빈 헤더가 나타납니다(구현 결과 반환).new String[0]머리말로)

커스텀 컬럼 이름과 오더를 모두 달성할 수 있는 유일한 방법은 커스텀을 작성하는 것입니다.MappingStrategy.

첫 번째 솔루션: 빠르고 쉬우면서도 코드화가 잘 되어 있습니다.

사용자 정의 만들기MappingStrategy:

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

그리고 사용합니다.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()

MappingsBean우리가 떠난 반CsvBindByPosition주석 - 순서를 제어합니다(이 솔루션의 경우)CsvBindByName주석은 필요 없습니다.사용자 지정 매핑 전략 덕분에 헤더 열 이름이 결과 CSV 파일에 포함됩니다.

이 솔루션의 단점은 컬럼 오더를 변경할 때CsvBindByPosition우리가 수동으로 변경해야 하는 주석.HEADER사용자 지정 매핑 전략에 일정합니다.

두 번째 솔루션: 유연성 향상

첫 번째 해결책은 효과가 있지만, 저에게는 좋지 않았습니다.기본 제공 구현을 기반으로 합니다.MappingStrategy저는 또 다른 구현 방법을 생각해 냈습니다.

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

이 사용자 지정 전략을 사용자 지정StatefulBeanToCsvBuilder첫 번째 솔루션에서와 정확히 동일합니다(실행할 remember).mappingStrategy.setType(MappingsBean.class);, 그렇지 않으면 이 솔루션이 작동하지 않습니다.

현재 저희의MappingsBean두 가지를 모두 포함해야 합니다.CsvBindByName그리고.CsvBindByPosition주석, 주석하고 두 헤더에 열 .첫 번째로 헤더 열 이름을 지정하고 두 번째로 출력 CSV 헤더에 열 순서를 작성합니다.이제 열 이름 또는 순서를 변경하면(주석 사용)MappingsBeanclass - 해당 변경 사항이 출력 CSV 파일에 반영됩니다.

위의 답변을 최신 버전과 일치하도록 수정했습니다.

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

그런 다음 이를 호출하여 CSV를 생성합니다.방문자를 POJO로 사용하여 필요한 곳에 채우고 업데이트했습니다.

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

내 콩 주석은 이렇게 생겼습니다.

 @CsvBindByName (column = "First Name", required = true)
 @CsvBindByPosition(position=1)
 private String firstName;


 @CsvBindByName (column = "Last Name", required = true)
 @CsvBindByPosition(position=0)
 private String lastName;

최신 버전에서는 @Sebast26의 솔루션이 더 이상 작동하지 않습니다.하지만 기본은 여전히 아주 좋습니다.다음은 v5.0과 함께 작동하는 솔루션입니다.

import com.opencsv.bean.BeanField;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.CsvBindByName;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
import org.apache.commons.lang3.StringUtils;

class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        final int numColumns = getFieldMap().values().size();
        super.generateHeader(bean);

        String[] header = new String[numColumns];

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

이 모델은 다음과 같습니다.

@CsvBindByName(column = "id")
@CsvBindByPosition(position = 0)
private Long id;
@CsvBindByName(column = "name")
@CsvBindByPosition(position = 1)
private String name;

그리고 우리 세대 도우미는 이런 모습입니다.

public static <T extends AbstractCsv> String createCsv(List<T> data, Class<T> beanClazz) {
    CustomMappingStrategy<T> mappingStrategy = new CustomMappingStrategy<T>();
    mappingStrategy.setType(beanClazz);

    StringWriter writer = new StringWriter();
    String csv = "";
    try {
        StatefulBeanToCsv sbc = new StatefulBeanToCsvBuilder(writer)
                .withSeparator(';')
                .withMappingStrategy(mappingStrategy)
                .build();
        sbc.write(data);
        csv = writer.toString();
    } catch (CsvRequiredFieldEmptyException e) {
        // TODO add some logging...
    } catch (CsvDataTypeMismatchException e) {
        // TODO add some logging...
    } finally {
        try {
            writer.close();
        } catch (IOException e) {
        }
    }
    return csv;
}

저는 양방향 수출입을 달성하고 싶었습니다. 즉, 생성된 CSV를 POJO로 다시 가져올 수 있고, 그 반대의 경우도 가능합니다.

이 경우 열 위치 매핑 전략이 자동으로 선택되었기 때문에 @CsvBindByPosition을 사용할 수 없었습니다.문서별: 이 전략을 사용하려면 파일에 헤더가 없어야 합니다.

목표를 달성하기 위해 사용한 것:

HeaderColumnNameMappingStrategy
mappingStrategy.setColumnOrderOnWrite(Comparator<String> writeOrder)

csvCSV 읽기/쓰기를 위한 CsvUtils

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

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
}

미리 정의된 문자열 순서 지정을 위한 사용자 지정 비교기:

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

POJO 주문서 작성(최초 질문에 대한 답변)

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

내보낸 CSV를 POJO로 다시 읽기(원래 답변에 추가)

중요: CSV는 이름별 바인딩을 여전히 사용하고 있으므로 주문하지 않을 수 있습니다.

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

결론:

  1. @CsvBindByName을 사용하고 있으므로 열 순서에 관계없이 CSV를 POJO로 읽을 수 있습니다.
  2. 사용자 지정 비교기를 사용하여 쓰기 시 열 순서를 제어할 수 있습니다.

다음은 POJO를 사용자 지정 열 위치 지정 및 사용자 지정 열 머리글(opencsv-5.0에서 테스트됨)을 사용하여 CSV 파일에 매핑하는 방법입니다.

public class CustomBeanToCSVMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {

    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {

        String[] headersAsPerFieldName = getFieldMap().generateHeader(bean); // header name based on field name

        String[] header = new String[headersAsPerFieldName.length];

        for (int i = 0; i <= headersAsPerFieldName.length - 1; i++) {

            BeanField beanField = findField(i);

            String columnHeaderName = extractHeaderName(beanField); // header name based on @CsvBindByName annotation

            if (columnHeaderName.isEmpty()) // No @CsvBindByName is present
                columnHeaderName = headersAsPerFieldName[i]; // defaults to header name based on field name

            header[i] = columnHeaderName;
        }

        headerIndex.initializeHeaderIndex(header);

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

포조

생성된 CSV 파일에서 Column Positioning(열 위치 지정):

  • 생성된 CSV 파일의 열 위치는 주석에 따라 달라집니다.@CsvBindByPosition

생성된 CSV 파일의 헤더 이름:

  • 필드에 있는 경우@CsvBindByName, 생성된 헤더는 주석에 따라 생성됩니다.
  • 필드에 없는 경우@CsvBindByName, 그러면 생성된 헤더는 필드 이름과 같습니다.

    @Getter @Setter @ToString
    public class Pojo {
    
        @CsvBindByName(column="Voucher Series") // header: "Voucher Series"
        @CsvBindByPosition(position=0)
        private String voucherSeries;
    
        @CsvBindByPosition(position=1) // header: "salePurchaseType"
        private String salePurchaseType;
    }
    

위의 사용자 지정 매핑 전략 사용:

CustomBeanToCSVMappingStrategy<Pojo> mappingStrategy = new CustomBeanToCSVMappingStrategy<>();
            mappingStrategy.setType(Pojo.class);

StatefulBeanToCsv<Pojo> beanToCsv = new StatefulBeanToCsvBuilder<Pojo>(writer)
                    .withSeparator(CSVWriter.DEFAULT_SEPARATOR)
                    .withMappingStrategy(mappingStrategy)
                    .build();

beanToCsv.write(pojoList);

이 실 덕분에 정말 유익했습니다...일부 필드에 주석이 달리지 않은(읽기/쓰기가 아닌) POJO를 수용하기 위해 제공된 솔루션을 약간 개선했습니다.

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

}

모델 클래스에서 멤버 변수가 나타나는 순서에 따라 CSV 열을 정렬하는 데만 관심이 있는 경우 (CsvRow이 예제에서 행)을 사용할 수 있습니다.Comparator이를 좀 더 간단한 방식으로 해결하기 위한 구현.Kotlin에서 이를 수행하는 예는 예입니다.

class ByMemberOrderCsvComparator : Comparator<String> {

    private val memberOrder by lazy {
        FieldUtils.getAllFields(CsvRow::class.java)
                .map { it.getDeclaredAnnotation(CsvBindByName::class.java) }
                .map { it?.column ?: "" }
                .map { it.toUpperCase(Locale.US) } // OpenCSV UpperCases all headers, so we do this to match
    }

    override fun compare(field1: String?, field2: String?): Int {
        return memberOrder.indexOf(field1) - memberOrder.indexOf(field2)
    }

}

.Comparator는 다음 작업을 수행합니다.

  1. 데이터 클래스의 각 멤버 변수 필드를 가져옵니다(CsvRow)
  2. 다음과 같은 모든 것을 찾습니다.@CsvBindByName주석(에서 지정한 순서대로)CsvRow델)
  3. 기본 OpenCsv 구현과 일치하는 상위 사례가 각각

그다음에 이걸 바르세요.Comparator당신에게MappingStrategy, 지정된 순서에 따라 정렬됩니다.

val mappingStrategy = HeaderColumnNameMappingStrategy<OrderSummaryCsvRow>()
mappingStrategy.setColumnOrderOnWrite(ByMemberOrderCsvComparator())
mappingStrategy.type = CsvRow::class.java
mappingStrategy.setErrorLocale(Locale.US)

val csvWriter = StatefulBeanToCsvBuilder<OrderSummaryCsvRow>(writer)
                    .withMappingStrategy(mappingStrategy)
                    .build()

참고로 여기 예가 있습니다.CsvRowclass(고객의 요구에 따라 이를 자신의 모델로 대체 가능:

data class CsvRow(
    @CsvBindByName(column = "Column 1")
    val column1: String,

    @CsvBindByName(column = "Column 2")
    val column2: String,

    @CsvBindByName(column = "Column 3")
    val column3: String,

    // Other columns here ...
)

다음과 같은 CSV를 생성합니다.

"COLUMN 1","COLUMN 2","COLUMN 3",...
"value 1a","value 2a","value 3a",...
"value 1b","value 2b","value 3b",...

이 방법의 장점은 열 이름을 하드 코드화할 필요가 없어지므로 열을 추가/제거해야 할 경우 작업을 크게 단순화할 수 있습니다.

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
}

이를 예로 들 수 있습니다.

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

결과:

column_c | column_b | column_a

다음 솔루션은 opencsv 5.0에서 작동합니다.

첫째, 당신은 상속해야 합니다.ColumnPositionMappingStrategy클래스 및 오버라이드generateHeader아래와 같이 CsvBindByName 및 CsvBindByPosition 주석을 모두 사용하기 위한 사용자 지정 헤더를 만드는 방법입니다.

import com.opencsv.bean.BeanField;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.CsvBindByName;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;

/**
 * @param <T>
 */
class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    /*
     * (non-Javadoc)
     * 
     * @see com.opencsv.bean.ColumnPositionMappingStrategy#generateHeader(java.lang.
     * Object)
     */
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        final int numColumns = getFieldMap().values().size();
        if (numColumns == -1) {
            return super.generateHeader(bean);
        }

        String[] header = new String[numColumns];
        super.setColumnMapping(header);

        BeanField<T, Integer> beanField;
        for (int i = 0; i < numColumns; i++) {
            beanField = findField(i);
            String columnHeaderName = beanField.getField().getDeclaredAnnotation(CsvBindByName.class).column();
            header[i] = columnHeaderName;
        }
        return header;
    }
}

다음 단계는 다음과 같이 CSV에 빈을 작성하면서 이 매핑 전략을 사용하는 것입니다.

CustomMappingStrategy<ScanReport> strategy = new CustomMappingStrategy<>();
            strategy.setType(ScanReport.class);

// Write a bean to csv file.
StatefulBeanToCsv<ScanReport> beanToCsv = new StatefulBeanToCsvBuilder<ScanReport>(writer)
                    .withMappingStrategy(strategy).build();
beanToCsv.write(beanList);

opencsv 최신 릴리스(4.6)를 사용하면서 사용하지 않는 API에 대한 모든 참조를 제거하여 이전 답변을 개선했습니다.

일반적인 코틀린 솔루션

/**
 * Custom OpenCSV [ColumnPositionMappingStrategy] that allows for a header line to be generated from a target CSV
 * bean model class using the following annotations when present:
 * * [CsvBindByName]
 * * [CsvCustomBindByName]
 */
class CustomMappingStrategy<T>(private val beanType: Class<T>) : ColumnPositionMappingStrategy<T>() {
    init {
        setType(beanType)
        setColumnMapping(*getAnnotatedFields().map { it.extractHeaderName() }.toTypedArray())
    }

    override fun generateHeader(bean: T): Array<String> = columnMapping

    private fun getAnnotatedFields() = beanType.declaredFields.filter { it.isAnnotatedByName() }.toList()

    private fun Field.isAnnotatedByName() = isAnnotationPresent(CsvBindByName::class.java) || isAnnotationPresent(CsvCustomBindByName::class.java)

    private fun Field.extractHeaderName() =
        getAnnotation(CsvBindByName::class.java)?.column ?: getAnnotation(CsvCustomBindByName::class.java)?.column ?: EMPTY
}

그런 다음 다음과 같이 사용합니다.

private fun csvBuilder(writer: Writer) =
    StatefulBeanToCsvBuilder<MappingsBean>(writer)
        .withSeparator(ICSVWriter.DEFAULT_SEPARATOR)
        .withMappingStrategy(CustomMappingStrategy(MappingsBean::class.java))
        .withApplyQuotesToAll(false)
        .build()

// Kotlin try-with-resources construct
PrintWriter(File("$reportOutputDir/$REPORT_FILENAME")).use { writer ->
    csvBuilder(writer).write(makeFinalMappingBeanList())
}

완성도를 위해 다음은 Cotlin 데이터 클래스로서의 CSV Bean입니다.

data class MappingsBean(
    @field:CsvBindByName(column = "TradeID")
    @field:CsvBindByPosition(position = 0, required = true)
    private val tradeId: String,

    @field:CsvBindByName(column = "GWML GUID", required = true)
    @field:CsvBindByPosition(position = 1)
    private val gwmlGUID: String,

    @field:CsvBindByName(column = "MXML GUID", required = true)
    @field:CsvBindByPosition(position = 2)
    private val mxmlGUID: String,

    @field:CsvBindByName(column = "GWML File")
    @field:CsvBindByPosition(position = 3)
    private val gwmlFile: String? = null,

    @field:CsvBindByName(column = "MxML File")
    @field:CsvBindByPosition(position = 4)
    private val mxmlFile: String? = null,

    @field:CsvBindByName(column = "MxML Counterparty")
    @field:CsvBindByPosition(position = 5)
    private val mxmlCounterParty: String? = null,

    @field:CsvBindByName(column = "GWML Counterparty")
    @field:CsvBindByPosition(position = 6)
    private val gwmlCounterParty: String? = null
)

헤더컬럼의 순서를 가장 유연하게 처리하는 방법은 HeaderColumnNameMappin Strategy에 의해 비교기를 주입하는 것이라고 생각합니다.ColumnOrderOnWrite()를 설정합니다.

저에게 가장 직관적인 방법은 CSV Bean에 지정된 것과 동일한 순서로 CSV 열을 작성하는 것이었지만, 비교기를 조정하여 순서를 지정하는 자체 주석을 사용할 수도 있습니다.그러면 Comparator 클래스 이름을 바꾸는 것을 잊지 마세요 ;)

통합:

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

비교기:

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

이것은 a를 사용하여 할 수 있습니다.HeaderColumnNameMappingStrategy관습에 따라서Comparator뿐만 아니라.공식 문서 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();

일반 클래스에 대한 사용자 지정 매핑 전략입니다.

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

POJO 클래스

 public class Customer{

     @CsvBindByPosition(position=1)
     @CsvBindByName(column="CUSTOMER", required = true)
     private String customer;
}

클라이언트 클래스

 List<T> data = getEmployeeRecord();
CustomMappingStrategy custom = new CustomMappingStrategy();
custom.setType(Employee.class);
StatefulBeanToCsv<T> writer = new StatefulBeanToCsvBuilder<T>(response.getWriter())
                .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
                .withSeparator('|')
                .withOrderedResults(false)
                .withMappingStrategy(custom)
                .build();
        writer.write(reportData);

위의 답변을 시도했을 때 @CsvCustomBindByName 주석에 문제가 있어서 5.2 버전은 다른 버전이 있습니다.

사용자 지정 주석을 정의했습니다.

@Target(ElementType.FIELD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface CsvPosition {

  int position();
}

사용자 지정 매핑 전략

public class CustomMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> {

  private final Field[] fields;

  public CustomMappingStrategy(Class<T> clazz) {
    fields = clazz.getDeclaredFields();
    Arrays.sort(fields, (f1, f2) -> {
      CsvPosition position1 = f1.getAnnotation(CsvPosition.class);
      CsvPosition position2 = f2.getAnnotation(CsvPosition.class);
      return Integer.compare(position1.position(), position2.position());
    });
  }

  @Override
  public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
    String[] header = new String[fields.length];
    for (Field f : fields) {
      CsvPosition position = f.getAnnotation(CsvPosition.class);
      header[position.position() - 1] = getName(f);
    }
    headerIndex.initializeHeaderIndex(header);
    return header;
  }

  private String getName(Field f) {
    CsvBindByName csvBindByName = f.getAnnotation(CsvBindByName.class);
    CsvCustomBindByName csvCustomBindByName = f.getAnnotation(CsvCustomBindByName.class);
    return csvCustomBindByName != null
      ? csvCustomBindByName.column() == null || csvCustomBindByName.column().isEmpty() ? f.getName() : csvCustomBindByName.column()
      : csvBindByName.column() == null || csvBindByName.column().isEmpty() ? f.getName() : csvBindByName.column();
  }

}

제 POJO 원두는 이렇게 주석이 달렸습니다.

public class Record {

  @CsvBindByName(required = true)
  @CsvPosition(position = 1)
  Long id;
  @CsvCustomBindByName(required = true, converter = BoolanCSVField.class)
  @CsvPosition(position = 2)
  Boolean deleted;
  ...
}

작가의 최종 코드:

CustomMappingStrategy<Record> mappingStrategy = new CustomMappingStrategy<>(Record.class);
mappingStrategy.setType(Record.class);
StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder(writer)
.withApplyQuotesToAll(false)
.withOrderedResults(true)
.withMappingStrategy(mappingStrategy)
.build();

누군가에게 도움이 되었으면 좋겠습니다.

지원을 추가할 코드는 다음과 같습니다.@CsvBindByPosition기본 순서를 기본 순서로 지정HeaderColumnNameMappingStrategy. 최신 버전으로 테스트됨5.2

접근 방법은 지도 2개를 저장하는 것입니다.첫번째headerPositionMap위치 요소를 저장하여 동일하게 사용할 수 있도록 합니다.setColumnOrderOnWrite,둘째columnMap대문자가 아닌 실제 열 이름을 검색할 수 있습니다.

public class HeaderColumnNameWithPositionMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> {

    protected Map<String, String> columnMap;

    @Override
    public void setType(Class<? extends T> type) throws CsvBadConverterException {
        super.setType(type);
        columnMap = new HashMap<>(this.getFieldMap().values().size());
        Map<String, Integer> headerPositionMap = new HashMap<>(this.getFieldMap().values().size());
        for (Field field : type.getDeclaredFields()) {
            if (field.isAnnotationPresent(CsvBindByPosition.class) && field.isAnnotationPresent(CsvBindByName.class)) {
                int position = field.getAnnotation(CsvBindByPosition.class).position();
                String colName = "".equals(field.getAnnotation(CsvBindByName.class).column()) ? field.getName() : field.getAnnotation(CsvBindByName.class).column();
                headerPositionMap.put(colName.toUpperCase().trim(), position);
                columnMap.put(colName.toUpperCase().trim(), colName);
            }
        }
        super.setColumnOrderOnWrite((String o1, String o2) -> {
            if (!headerPositionMap.containsKey(o1) || !headerPositionMap.containsKey(o2)) {
                return 0;
            }
            return headerPositionMap.get(o1) - headerPositionMap.get(o2);
        });
    }

    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        String[] headersRaw = super.generateHeader(bean);
        return Arrays.stream(headersRaw).map(h -> columnMap.get(h)).toArray(String[]::new);
    }
}

선언 주석을 받지 못한 경우메서드를 입력하지만 원래 필드 이름이 필요합니다.

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

}

다음과 같은 방법을 사용해 보십시오.

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

그런 다음 다음과 같이 사용합니다.

String[] columns = new String[]{"Name", "Age", "Company", "Salary"};
        CustomMappingStrategy<Employee> mappingStrategy = new CustomMappingStrategy<Employee>(columns);

열은 빈의 열이고 종업원은 빈의 열입니다.

좋은 스레드, 나는 내 포조에 주석이 없고 이전의 모든 답변을 바탕으로 이렇게 했습니다.다른 사람들에게 도움이 되길 바랍니다.

OpenCsv 버전: 5.0 읽기 벤더 목록 = getFromMethod(); String[] 필드 = {"id", recordNumber", finVendorIdTb", finVenTechIdTb", finShortNameTb", finVenName1Tb", finVenName2Tb",

            String[] csvHeader= {"Id#","Shiv Record Number","Shiv Vendor Id","Shiva Tech Id#","finShortNameTb","finVenName1Tb","finVenName2Tb"};

            CustomMappingStrategy<FinVendor> mappingStrategy = new CustomMappingStrategy(csvHeader);//csvHeader as per custom header irrespective of pojo field name
            mappingStrategy.setType(FinVendor.class);
            mappingStrategy.setColumnMapping(fields);//pojo mapping fields


            StatefulBeanToCsv<FinVendor> beanToCsv = new StatefulBeanToCsvBuilder<FinVendor>(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER).withMappingStrategy(mappingStrategy).build();
            beanToCsv.write(readVendors);

//많은 사용자가 스레드에서 언급한 사용자 지정 정적 클래스 CustomMappingStrategy(사용자 지정 매핑 전략)는 ColumnPositionMappingStrategy(열 위치 매핑 전략)를 확장합니다.

                    String[] header;

                    public CustomMappingStrategy(String[] cols) {

                            header = cols;
                    }

                    @Override
                    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
                        super.generateHeader(bean);
                            return header;
                    }
                    }

출력:

Id#     Shiv Record Number      Shiv Vendor Id   Fin Tech Id#      finShortNameTb  finVenName1Tb   finVenName2Tb   finVenDefaultLocTb
1       VEN00053                678             33316025986        THE ssOHIO S_2  THE UNIVERSITY     CHK         Test
2       VEN02277                1217            3044374205         Fe3 MECHA_1     FR3INC             EFT-1
3       VEN03118                1310            30234484121        PE333PECTUS_1   PER332CTUS AR      EFT-1       Test

sebast26의 첫 번째 솔루션은 효과가 있었지만 opencsv 버전 5.2의 경우 CustomMapping Strategy 클래스를 약간 변경해야 합니다.

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

}

원래 CSV에서 열 순서를 유지하기 위해 필요한 경우: 읽기를 위해 HeaderColumnNameMapping Strategy를 사용한 다음 동일한 쓰기 전략을 사용합니다.이 경우 "Same"은 같은 클래스일 뿐만 아니라 실제로도 같은 객체를 의미합니다.

자바독에서StatefulBeanToCsvBuilder.withMappingStrategy:

CSV 소스를 읽고 읽기 작업에서 매핑 전략을 가져온 후 쓰기 작업을 위해 이 방법으로 전달하는 것은 완벽하게 합법적입니다.이렇게 하면 처리 시간이 다소 절약되지만, 더 중요한 것은 헤더 순서를 유지한다는 입니다.

이렇게 하면 원래 CSV와 같은 순서로 열이 있는 헤더를 포함한 CSV를 얻을 수 있습니다.

OpenCSV 5.4를 사용하여 작업했습니다.

저도 시간이 걸렸지만 해결책을 찾았습니다.

각 개체의 이름과 위치가 올바른 @CsvBindByName, @CsvBindByPosition 등 POJO에 주석을 추가합니다.

내 POJO:

@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
@Setter
public class CsvReport {
    @CsvBindByName(column = "Campaign")
    @CsvBindByPosition(position = 0)
    private String program;
    @CsvBindByName(column = "Report")
    @CsvBindByPosition(position = 1)
    private String report;
    @CsvBindByName(column = "Metric Label")
    @CsvBindByPosition(position = 2)
    private String metric;
}

그리고 이 코드(CsvReport라는 나의 POJO)를 추가합니다.

ColumnPositionMappingStrategy<CsvReport> mappingStrategy = new ColumnPositionMappingStrategyBuilder<CsvReport>().build();
mappingStrategy.setType(CsvReport.class);
//add your headers in the sort you want to be in the file:
String[] columns = new String[] { "Campaign", "Report", "Metric Label"};
mappingStrategy.setColumnMapping(columns);

//Write your headers first in your chosen Writer:
Writer responseWriter = response.getWriter();
responseWriter.append(String.join(",", columns)).append("\n");

// Configure the CSV writer builder
StatefulBeanToCsv<CsvReport> writer = new StatefulBeanToCsvBuilder<CsvReport>(responseWriter)
                    .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
                    .withSeparator(CSVWriter.DEFAULT_SEPARATOR)
                    .withOrderedResults(true) //I needed to keep the order, if you don't put false.
                    .withMappingStrategy(mappingStrategy)
                    .build();

String fileName = "your file name";
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,String.format("attachment; filename=%s", fileName));
writer.write(csvReports);

이렇게 하면 인쇄된 머리글과 순서가 지정된 필드로 새 CSV 파일이 만들어집니다.

그라들 의존성 이하로 테스트된 코드

implementation 'com.opencsv:opencsv:5.7.1'

단순히 헤더를 생성하려면 코드 아래에 쓰면 됩니다. 하드코드 헤더 값이 필요 없습니다.

public class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
  @Override
  public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
    return getFieldMap().generateHeader(bean);
  }
}
public class BeanToCSVWriter {
    
    public <T> String write(List<T> beans, Class<T> beanType, String[] headerColumns) {
        ColumnPositionMappingStrategy<T> mappingStrategy = new CustomMappingStrategy<>();
        mappingStrategy.setColumnMapping(headerColumns);
        mappingStrategy.setType(beanType);
        
        Writer writer = new StringWriter();
        
        StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(writer)
                .withMappingStrategy(mappingStrategy)
                .build();
        
        try {
            beanToCsv.write(beans);
        } catch (CsvDataTypeMismatchException | CsvRequiredFieldEmptyException exception) {
            throw new RuntimeException(exception);
        }
    }
}

위 코드 사용방법

BeanToCSVWriter beanToCSVWriter = new BeanToCSVWriter();
String[] headerColumns = {id, name, age, permanentAddress};

beanToCSVWriter.write(beans, Bean.class, headerColumns)

언급URL : https://stackoverflow.com/questions/45203867/opencsv-how-to-create-csv-file-from-pojo-with-custom-column-headers-and-custom

반응형