You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用OpenCSV将含嵌套Bean的对象映射为指定列的CSV?

解决OpenCSV导出嵌套List属性到指定CSV列的问题

我来帮你解决这个问题!你遇到的核心问题是OpenCSV的默认ColumnPositionMappingStrategy没法自动处理嵌套集合属性并提取其中的指定字段,同时你需要把一个Student对应的多本Book展开成CSV的多行记录。下面给你两种可行的解决方案,其中第一种更简单直观,推荐优先使用:

方案一:先将嵌套结构扁平化为DTO

这种方法的思路是先把Student和对应的Book组合成一个扁平化的DTO(数据传输对象),让每个DTO对应CSV中的一行记录,再用OpenCSV导出这个DTO列表。

1. 创建扁平化的DTO类

class FlatStudentDTO {
    @CsvBindByName(column = "s_id")
    private String sId;
    @CsvBindByName(column = "name")
    private String name;
    @CsvBindByName(column = "bookName")
    private String bookName;

    // 构造方法,用于从Student和Book生成DTO
    public FlatStudentDTO(Student student, Book book) {
        this.sId = student.getS_id();
        this.name = student.getName();
        this.bookName = book != null ? book.getBookName() : "";
    }

    // 必须提供无参构造方法(OpenCSV需要)
    public FlatStudentDTO() {}

    // 生成getter和setter方法
    public String getSId() { return sId; }
    public void setSId(String sId) { this.sId = sId; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getBookName() { return bookName; }
    public void setBookName(String bookName) { this.bookName = bookName; }
}

2. 转换原始数据为DTO列表

把你的Student列表转换成FlatStudentDTO列表,每个Book对应一个DTO记录:

List<FlatStudentDTO> flatStudentList = new ArrayList<>();
for (Student student : students) {
    List<Book> books = student.getBooks();
    if (books == null || books.isEmpty()) {
        // 如果学生没有书,生成一条bookName为空的记录
        flatStudentList.add(new FlatStudentDTO(student, null));
    } else {
        // 每本书对应一条记录
        for (Book book : books) {
            flatStudentList.add(new FlatStudentDTO(student, book));
        }
    }
}

3. 使用OpenCSV导出DTO

这时候用默认的映射策略就可以轻松导出了,不需要复杂的配置:

StatefulBeanToCsv<FlatStudentDTO> btcsv = new StatefulBeanToCsvBuilder<FlatStudentDTO>(writer)
        .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
        .withSeparator(';')
        .build();
btcsv.write(flatStudentList);

方案二:自定义MappingStrategy(进阶)

如果不想创建额外的DTO类,可以自定义MappingStrategy来处理嵌套集合的展开和字段提取。不过这种方法需要你手动处理每行数据的生成:

public class CustomStudentMappingStrategy extends ColumnPositionMappingStrategy<Student> {
    @Override
    public String[] generate(Student student) throws CsvRequiredFieldEmptyException {
        // 注意:OpenCSV的StatefulBeanToCsv默认一个Bean对应一行,所以如果要生成多行,需要先拆分Bean
        // 这里我们还是建议用DTO方案,因为自定义策略无法直接让一个Bean生成多行,需要配合其他扩展逻辑
        // 如果你坚持用这种方式,可能需要自己遍历集合并手动写入CSV行,而不是依赖StatefulBeanToCsv的write方法
        String[] row = new String[3];
        row[0] = student.getS_id();
        row[1] = student.getName();
        // 这里只能取第一本书的名字,无法自动展开所有书,所以DTO方案更适合你的需求
        row[2] = student.getBooks() != null && !student.getBooks().isEmpty() 
            ? student.getBooks().get(0).getBookName() 
            : "";
        return row;
    }
}

不过这种自定义策略只能取集合中的第一个元素,无法自动展开所有Book成多行,所以还是方案一的DTO方法更符合你的需求。

为什么你的原始代码不生效?

你的原始代码中,ColumnPositionMappingStrategy只能识别Student类的直接属性(s_id、name),对于嵌套的List<Book>属性,它无法自动遍历集合并提取bookName字段;而@CsvRecurse注解是用来递归导出整个Book对象的所有属性,不是提取单个字段,所以无法得到你想要的结果。

内容的提问来源于stack exchange,提问作者SatyaKinkar Gorai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 06:57:27