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

Spring Boot控制器返回嵌套JSON转CSV文件实现方案咨询

嵌套JSON转CSV的可行方案(Spring Boot环境)

常规JSON转CSV工具失效的核心原因是存在嵌套对象(如示例中的team),这类工具无法自动解析嵌套字段。解决思路是先扁平化嵌套结构,将嵌套对象的字段拆分为CSV的独立列,再完成转换。以下是三种实用方案:


方案一:手动扁平化+Apache Commons CSV(灵活可控)

适合结构固定的场景,直接提取嵌套字段到顶层,手动构建CSV行。

  1. 定义实体类(也可直接用Map处理,实体类更易维护):
// 球员实体
public class Player {
    private Integer id;
    private String firstName;
    private String lastName;
    private String position;
    private Integer heightFeet;
    private Integer heightInches;
    private Integer weightPounds;
    private Team team;

    // getter、setter、构造器
}

// 球队实体
public class Team {
    private Integer id;
    private String abbreviation;
    private String city;
    private String conference;
    private String division;
    private String fullName;
    private String name;

    // getter、setter、构造器
}
  1. 将JSON转为实体对象:
ObjectMapper objectMapper = new ObjectMapper();
Player player = objectMapper.readValue(apiJsonResponse, Player.class);
  1. 用Apache Commons CSV生成文件:
    先引入依赖:
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-csv</artifactId>
    <version>1.10.0</version>
</dependency>

转换代码:

// 定义CSV表头,包含扁平化后的字段
String[] HEADERS = {
    "id", "first_name", "last_name", "position", "height_feet",
    "height_inches", "weight_pounds", "team_id", "team_abbreviation",
    "team_city", "team_conference", "team_division", "team_full_name", "team_name"
};

// 写入到响应流(可直接输出给前端下载)
try (CSVPrinter printer = new CSVPrinter(new OutputStreamWriter(response.getOutputStream()), CSVFormat.DEFAULT.builder()
        .setHeader(HEADERS)
        .setSkipHeaderRecord(false)
        .build())) {
    // 手动提取嵌套字段,写入一行数据
    printer.printRecord(
        player.getId(),
        player.getFirstName(),
        player.getLastName(),
        player.getPosition(),
        player.getHeightFeet(),
        player.getHeightInches(),
        player.getWeightPounds(),
        player.getTeam().getId(),
        player.getTeam().getAbbreviation(),
        player.getTeam().getCity(),
        player.getTeam().getConference(),
        player.getTeam().getDivision(),
        player.getTeam().getFullName(),
        player.getTeam().getName()
    );
} catch (IOException e) {
    // 业务异常处理
    e.printStackTrace();
}

方案二:Jackson自定义CSV序列化(自动扁平化)

适合需要通用处理的场景,通过Jackson的CsvSchema配置实现嵌套字段的自动映射。

  1. 引入Jackson CSV依赖:
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-csv</artifactId>
    <version>2.15.2</version>
</dependency>
  1. 序列化代码:
ObjectMapper objectMapper = new ObjectMapper(new CsvMapper());

// 构建CSV Schema,指定嵌套字段的列名
CsvSchema schema = CsvSchema.builder()
        .addColumn("id")
        .addColumn("first_name")
        .addColumn("last_name")
        .addColumn("position")
        .addColumn("height_feet")
        .addColumn("height_inches")
        .addColumn("weight_pounds")
        .addColumn("team_id", CsvSchema.ColumnType.NUMBER)
        .addColumn("team_abbreviation")
        .addColumn("team_city")
        .addColumn("team_conference")
        .addColumn("team_division")
        .addColumn("team_full_name")
        .addColumn("team_name")
        .setUseHeader(true)
        .build();

// 配置序列化规则
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

// 转换为CSV字符串并输出
String csvContent = objectMapper.writer(schema).writeValueAsString(player);
response.setContentType("text/csv");
response.setHeader("Content-Disposition", "attachment; filename=player.csv");
response.getWriter().write(csvContent);

方案三:OpenCSV配合注解映射

通过OpenCSV的注解直接在实体类上绑定CSV列名,自动完成转换。

  1. 引入OpenCSV依赖:
<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>5.6</version>
</dependency>
  1. 修改实体类添加注解:
public class Player {
    @CsvBindByName(column = "id")
    private Integer id;
    @CsvBindByName(column = "first_name")
    private String firstName;
    @CsvBindByName(column = "last_name")
    private String lastName;
    @CsvBindByName(column = "position")
    private String position;
    @CsvBindByName(column = "height_feet")
    private Integer heightFeet;
    @CsvBindByName(column = "height_inches")
    private Integer heightInches;
    @CsvBindByName(column = "weight_pounds")
    private Integer weightPounds;
    
    private Team team;

    // 为嵌套字段添加映射Getter,供OpenCSV读取
    @CsvBindByName(column = "team_id")
    public Integer getTeamId() {
        return team != null ? team.getId() : null;
    }

    @CsvBindByName(column = "team_abbreviation")
    public String getTeamAbbreviation() {
        return team != null ? team.getAbbreviation() : null;
    }

    // 其他team字段的映射Getter同理...

    // 原有的getter、setter
}
  1. 转换代码:
try (Writer writer = new OutputStreamWriter(response.getOutputStream())) {
    StatefulBeanToCsv<Player> beanToCsv = new StatefulBeanToCsvBuilder<Player>(writer)
            .withSeparator(CSVWriter.DEFAULT_SEPARATOR)
            .withQuotechar(CSVWriter.DEFAULT_QUOTE_CHARACTER)
            .withOrderedResults(true)
            .build();
    beanToCsv.write(player);
} catch (CsvDataTypeMismatchException | CsvRequiredFieldEmptyException | IOException e) {
    e.printStackTrace();
}

关键注意点

  • 无论哪种方案,核心都是扁平化嵌套结构,必须将嵌套对象的字段拆分为CSV的独立列。
  • 如果处理批量数据,只需循环写入每个对象即可。
  • 注意空值处理,避免嵌套对象为null时出现空指针异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 04:15:42