web-dev-qa-db-ja.com

OpenCSV:カスタム列ヘッダーとカスタム列位置を使用してPOJOからCSVファイルを作成する方法

CSVファイルのすべての列が指定されたMappingsBeanクラスを作成しました。次に、XMLファイルを解析し、マッピングBeanのリストを作成します。次に、そのデータをレポートとして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;
}
_

そして、StatefulBeanToCsvクラスを使用してCSVファイルに書き込みます。

_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ファイルを作成し、列の位置を制御できる方法はありますか?

よろしく、Vikram Pathania

25
Vikram Pathania

同様の問題が発生しました。私の知る限り、OpenCSVには、カスタム列名and順序付けでCSVにBeanを書き込むことを可能にする組み込み機能はありません。

OpenCSVですぐに使用できる2つの主なMappingStrategyiesがあります。

  • HeaderColumnNameMappingStrategy:カスタム名に基づいてCVSファイルの列をBeanフィールドにマッピングできます。 BeanをCSVに書き込むと、列ヘッダー名を変更できますが、列の順序を制御できません
  • ColumnPositionMappingStrategy:列の順序に基づいてCSVファイルの列をBeanフィールドにマッピングできます。 Beanを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定数も手動で変更する必要があることです。

2番目のソリューション:より柔軟な

最初の解決策は機能しますが、私にとっては良くありませんでした。 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で使用できます(mappingStrategy.setType(MappingsBean.class);を呼び出すことを忘れないでください。そうしないと、このソリューションは機能しません)。

現在、MappingsBeanにはCsvBindByNameCsvBindByPositionの両方の注釈を含める必要があります。 1つ目はヘッダー列名を指定し、2つ目は出力CSVヘッダーの列の順序を作成します。ここで(注釈を使用して)列名またはMappingsBeanクラスの順序を変更すると、その変更は出力CSVファイルに反映されます。

22
sebast26

上記の回答を新しいバージョンに合わせて修正しました。

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

私のBeanアノテーションは次のようになります

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


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

トピックスターターと同じ結果を達成したかったが、生成されたCSVをPOJOにインポートできるようにしたかった。それを達成するのに役立つソリューションはありませんでした。

結果を得るには、@ CsvBindByPositionの使用を拒否する必要がありました。この場合-ColumnPositionMappingStrategyが自動的に選択されたためです。ドキュメントごと: この戦略では、ファイルにヘッダーがないことが必要です

ただし、列名によるバインディングを使用してopenCSVで記述した場合、ヘッダーが自動的に追加されます(ヘッダーは削除できますが、便利です)。

私が目標を達成するために使用したこと:

HeaderColumnNameMappingStrategy
mappingStrategy.setColumnOrderOnWrite(Comparator<String> writeOrder)

csvUtilsによるcsvの読み取り/書き込み

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も受け入れるために、提供されているソリューションを少し強化しました。

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

}

2
Rodrigo Broggi

モデルクラス(この例ではCsvRow行)に表示されるメンバー変数の順序に基づいてCSV列を並べ替えるだけの場合は、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実装に一致する大文字

次に、このComparatorMappingStrategyに適用すると、指定された順序に基づいてソートされます。

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

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

参考のために、以下にCsvRowクラスの例を示します(これを必要に応じて独自のモデルに置き換えます)。

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",...

このアプローチの利点は、列名をハードコーディングする必要がなくなることです。これにより、列を追加/削除する必要がある場合に物事が大幅に簡素化されます。

1
wrb

以下のようなものを試してください:

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

列はBeanの列であり、従業員はBeanです

0
Kalya Elisha

GetDeclaredAnnotationsByTypeメソッドはないが、元のフィールド名が必要な場合:

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

}

0
akasha