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

如何优雅地将CSV数据读取至Immutable对象?

如何将CSV数据直接映射到Immutable对象?

问题场景

我基于CSV数据定义了一个Immutable视图:

@Value.Style(stagedBuilder = true)
@Value.Immutable
public interface MyImmutablePerson {
    String getName();
    Optional<Integer> getAge();
    Optional<String> getAddress();
}

对应的CSV数据如下:

name,age,address
John,10,123 Main St
Jack,11,123 Main St
Jake,12,123 Main St

目前只能手动拆分CSV行数据并构建对象:

String[] record = reader.readLine().split(',');

ImmutableMyImmutablePerson.builder()
    .name(record[0])
    .age(record[1])
    .address(record[2])
    .build();

但实际场景存在大量列,手动构建过于繁琐,希望找到能直接将CSV行映射到Immutable对象的CSV库。

可行方案

1. Jackson CSV 数据绑定(最简洁)

Jackson支持CSV格式的序列化与反序列化,只需给Immutable接口添加@JsonDeserialize注解指定Builder类,即可自动完成映射,还能处理Optional类型的转换。

步骤1:给Immutable接口添加注解

@Value.Style(stagedBuilder = true)
@Value.Immutable
@JsonDeserialize(builder = ImmutableMyImmutablePerson.Builder.class)
public interface MyImmutablePerson {
    String getName();
    Optional<Integer> getAge();
    Optional<String> getAddress();
}

步骤2:读取CSV示例

public List<MyImmutablePerson> readWithJackson(File file) throws IOException {
    CsvMapper mapper = new CsvMapper();
    // 使用CSV表头作为字段匹配依据
    CsvSchema schema = CsvSchema.emptySchema().withHeader();
    MappingIterator<MyImmutablePerson> iterator = mapper.readerFor(MyImmutablePerson.class)
            .with(schema)
            .readValues(file);
    return iterator.readAll();
}

2. OpenCSV(自定义映射策略)

OpenCSV通过自定义MappingStrategy适配Immutable的Builder模式,适合需要精细控制映射逻辑的场景。

示例代码

// 自定义映射策略,返回Immutable对象的Builder实例
public class MyImmutablePersonMappingStrategy extends ColumnPositionMappingStrategy<ImmutableMyImmutablePerson.Builder> {
    @Override
    public ImmutableMyImmutablePerson.Builder createBean() throws InstantiationException, IllegalAccessException {
        return ImmutableMyImmutablePerson.builder();
    }
}

// 读取CSV并映射
public List<MyImmutablePerson> readCsv(File file) throws IOException {
    try (CSVReader reader = new CSVReader(new FileReader(file))) {
        MyImmutablePersonMappingStrategy strategy = new MyImmutablePersonMappingStrategy();
        strategy.setType(ImmutableMyImmutablePerson.Builder.class);
        // 绑定CSV列与Builder方法
        strategy.setColumnMapping("name", "age", "address");

        CsvToBean<ImmutableMyImmutablePerson.Builder> csvToBean = new CsvToBeanBuilder<ImmutableMyImmutablePerson.Builder>(reader)
                .withMappingStrategy(strategy)
                .withSkipLines(1) // 跳过表头行
                .build();

        // 将Builder实例构建为最终Immutable对象
        return csvToBean.parse().stream()
                .map(ImmutableMyImmutablePerson.Builder::build)
                .collect(Collectors.toList());
    }
}

3. Apache Commons CSV(反射调用Builder)

利用Apache Commons CSV读取行数据,通过反射自动调用Builder的对应方法,适合列名与Builder方法名完全匹配的场景。

示例代码

public List<MyImmutablePerson> readWithCommonsCsv(File file) throws IOException {
    List<MyImmutablePerson> result = new ArrayList<>();
    try (CSVParser parser = CSVParser.parse(file, StandardCharsets.UTF_8, CSVFormat.DEFAULT.withHeader())) {
        for (CSVRecord record : parser) {
            ImmutableMyImmutablePerson.Builder builder = ImmutableMyImmutablePerson.builder();
            // 遍历所有表头,反射调用Builder方法
            for (String header : parser.getHeaderNames()) {
                String value = record.get(header);
                if (value != null && !value.isBlank()) {
                    try {
                        // 根据列名匹配Builder方法
                        Method method = builder.getClass().getMethod(header, getParameterType(header));
                        method.invoke(builder, convertValue(value, getParameterType(header)));
                    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                        throw new RuntimeException("映射列" + header + "失败", e);
                    }
                }
            }
            result.add(builder.build());
        }
    }
    return result;
}

// 辅助方法:根据列名获取对应参数类型
private Class<?> getParameterType(String header) {
    return switch (header) {
        case "age" -> Integer.class;
        case "name", "address" -> String.class;
        default -> throw new IllegalArgumentException("未知列:" + header);
    };
}

// 辅助方法:将CSV字符串值转换为对应类型
private Object convertValue(String value, Class<?> type) {
    if (type == Integer.class) {
        return Integer.parseInt(value);
    }
    return value;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 20:27:48