使用 OpenCSV 将 CSV 解析为多个/嵌套 bean 类型?

2023-11-24

我有各种 CSV,其中包含一些标准列和一些完全随机的字段:

firstname, lastname, dog_name, fav_hat, fav_color
bill,smith,fido,porkpie,blue
james,smith,rover,bowler,purple


firstname, lastname, car_type, floor_number
tom, collins, ford, 14
jim, jones, toyota, 120

所以我试图将它们解析为 Person.class bean,它保存名字和姓氏,然后我有一个名为 PersonAttribute.class 的第二个类来保存......其他什么。

两个类的基本概要:

class Person {
 public String firstname;
 public String lastname;
 public List<PersonAttribute> attribs;
}

class PersonAttribute {
 public Person p;
 public String key; // header name, ex. 'car_type'
 public String value; // column value, ex. 'ford'
}

我一直在 opencsv 中使用 CsvToBean 函数:

public static List<Person> parseToBeans(File csvFile, HashMap<String, String> mapStrategy, Class beanClass) throws IOException {
    CSVReader reader = null;
    try {
        reader = new CSVReader(new BufferedReader(new FileReader(csvFile)));

        HeaderColumnNameTranslateMappingStrategy<Person> strategy = new HeaderColumnNameTranslateMappingStrategy<>();
        strategy.setType(beanClass);
        strategy.setColumnMapping(mapStrategy);

        final CsvToBean<Person> csv = new CsvToBean<Person>() {
            @Override
            protected Object convertValue(String value, PropertyDescriptor prop) throws InstantiationException, IllegalAccessException {
                value = value.trim().replaceAll(" +", " ");
                return super.convertValue(value, prop);
            }
        };
        return csv.parse(strategy, reader);
    }
...

但是,我不确定在解析 Person.class beans 的 csv 时如何处理创建 PersonAttribute.class beans。我碰到这个帖子我想知道我是否需要切换到 supercsv 才能轻松处理我想做的事情?


您当然可以使用 Super CSV 来实现这一目标。

您可以使用

  • CsvBeanReader- 不支持索引映射,因此您需要在 bean 中创建一个辅助方法才能使用它

  • CsvDozerBeanReader- 支持开箱即用的索引映射,因此将完全满足您的需求(需要最近发布的 Super CSV 2.1.0)

使用 CsvBeanReader

如果您不想使用 Dozer 并且能够修改您的 bean 类,最简单的选择是在您的 bean 上添加一个虚拟 setterCsvBeanReader将用于填充属性。我假设你的Person and PersonAttributebeans 有一个公共的无参数构造函数和为每个字段定义的 getters/setters(这是必需的)。

将以下虚拟设置器添加到您的Person bean:

public void setAddAttribute(PersonAttribute attribute){
    if (attribs == null){
        attribs = new ArrayList<PersonAttribute>();
    }
    attribs.add(attribute);
}

创建自定义细胞处理器这将填充一个PersonAttribute使用 CSV 标头中的相应键以及 CSV 列中的值。

package org.supercsv.example;

import org.supercsv.cellprocessor.CellProcessorAdaptor;
import org.supercsv.util.CsvContext;

/**
 * Creates a PersonAttribute using the corresponding header as the key.
 */
public class ParsePersonAttribute extends CellProcessorAdaptor {

    private final String[] header;

    public ParsePersonAttribute(final String[] header) {
        this.header = header;
    }

    public Object execute(Object value, CsvContext context) {

        if( value == null ) {
            return null;
        }

        PersonAttribute attribute = new PersonAttribute();
        // columns start at 1
        attribute.setKey(header[context.getColumnNumber() - 1]);
        attribute.setValue((String) value);
        return attribute;
    }

}

我认为下面的例子已经很能说明问题了,但我应该指出以下几点:

  • 我必须使用自定义首选项,因为您的 CSV 包含不属于数据的空格

  • 我必须动态组装字段映射和单元处理器数组,因为您的数据具有未知数量的属性(此设置通常并不复杂)

  • 属性使用的所有字段映射addAttribute,对应于setAddAttribute()我们添加到您的 bean 中的方法

  • 我使用我们的定制细胞处理器创建了一个PersonAttribute每个属性列的 bean

这是代码:

package org.supercsv.example;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.prefs.CsvPreference;

public class ReadWithCsvBeanReader {

    private static final String CSV = 
            "firstname, lastname, dog_name, fav_hat, fav_color\n"
            + "bill,smith,fido,porkpie,blue\n"
            + "james,smith,rover,bowler,purple";

    private static final String CSV2 = 
            "firstname, lastname, car_type, floor_number\n"
            + "tom, collins, ford, 14\n" + "jim, jones, toyota, 120";

    // attributes start at element 2 of the header array
    private static final int ATT_START_INDEX = 2;

    // custom preferences required because CSV contains 
    spaces that aren't part of the data
    private static final CsvPreference PREFS = 
        new CsvPreference.Builder(
            CsvPreference.STANDARD_PREFERENCE)
            .surroundingSpacesNeedQuotes(true).build();

    public static void main(String[] args) throws IOException {
        System.out.println("CsvBeanReader with first CSV input:");
        readWithCsvBeanReader(new StringReader(CSV));
        System.out.println("CsvBeanReader with second CSV input:");
        readWithCsvBeanReader(new StringReader(CSV2));
    }

    private static void readWithCsvBeanReader(final Reader reader)
            throws IOException {
        ICsvBeanReader beanReader = null;
        try {
            beanReader = new CsvBeanReader(reader, PREFS);

            final String[] header = beanReader.getHeader(true);

            // set up the field mapping and processors dynamically
            final String[] fieldMapping = new String[header.length];
            final CellProcessor[] processors = 
                    new CellProcessor[header.length];
            for (int i = 0; i < header.length; i++) {
                if (i < ATT_START_INDEX) {
                    // normal mappings
                    fieldMapping[i] = header[i];
                    processors[i] = new NotNull();
                } else {
                    // attribute mappings
                    fieldMapping[i] = "addAttribute";
                    processors[i] = 
                            new Optional(new ParsePersonAttribute(header));
                }
            }

            Person person;
            while ((person = beanReader.read(Person.class, fieldMapping,
                    processors)) != null) {
                System.out.println(String.format(
                        "lineNo=%s, rowNo=%s, person=%s",
                        beanReader.getLineNumber(), beanReader.getRowNumber(),
                        person));
            }

        } finally {
            if (beanReader != null) {
                beanReader.close();
            }
        }
    }

}

输出(我添加了toString()方法到你的豆子):

CsvBeanReader with first CSV input:
lineNo=2, rowNo=2, person=Person [firstname=bill, lastname=smith, attribs=[PersonAttribute [key=dog_name, value=fido], PersonAttribute [key=fav_hat, value=porkpie], PersonAttribute [key=fav_color, value=blue]]]
lineNo=3, rowNo=3, person=Person [firstname=james, lastname=smith, attribs=[PersonAttribute [key=dog_name, value=rover], PersonAttribute [key=fav_hat, value=bowler], PersonAttribute [key=fav_color, value=purple]]]
CsvBeanReader with second CSV input:
lineNo=2, rowNo=2, person=Person [firstname=tom, lastname=collins, attribs=[PersonAttribute [key=car_type, value=ford], PersonAttribute [key=floor_number, value=14]]]
lineNo=3, rowNo=3, person=Person [firstname=jim, lastname=jones, attribs=[PersonAttribute [key=car_type, value=toyota], PersonAttribute [key=floor_number, value=120]]]

使用 CsvDozerBeanReader

如果你不能,或者不想修改你的bean,那么我建议使用CsvDozerBeanReader in the 超级 CSV 推土机扩展装置项目,因为它支持嵌套和索引字段映射。查看一些正在使用的示例here.

下面是一个使用的示例CsvDozerBeanReader。你会发现它几乎与CsvBeanReader示例,但是:

  • 它使用不同的阅读器(废话!)

  • 它使用索引映射,例如attribs[0]

  • 它通过调用来设置映射configureBeanMapping()(而不是接受字符串数组read()方法就像CsvBeanReader

  • 它还设置了一些提示(更多内容见下文)

Code:

package org.supercsv.example;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.dozer.CsvDozerBeanReader;
import org.supercsv.io.dozer.ICsvDozerBeanReader;
import org.supercsv.prefs.CsvPreference;

public class ReadWithCsvDozerBeanReader {

    private static final String CSV = 
            "firstname, lastname, dog_name, fav_hat, fav_color\n"
            + "bill,smith,fido,porkpie,blue\n" 
            + "james,smith,rover,bowler,purple";

    private static final String CSV2 = 
            "firstname, lastname, car_type, floor_number\n" 
            + "tom, collins, ford, 14\n"
            + "jim, jones, toyota, 120";

    // attributes start at element 2 of the header array
    private static final int ATT_START_INDEX = 2;

    // custom preferences required because CSV contains spaces that aren't part of the data
    private static final CsvPreference PREFS = new CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE)
        .surroundingSpacesNeedQuotes(true).build();

    public static void main(String[] args) throws IOException {
        System.out.println("CsvDozerBeanReader with first CSV input:");
        readWithCsvDozerBeanReader(new StringReader(CSV));
        System.out.println("CsvDozerBeanReader with second CSV input:");
        readWithCsvDozerBeanReader(new StringReader(CSV2));
    }

    private static void readWithCsvDozerBeanReader(final Reader reader) throws IOException {
        ICsvDozerBeanReader beanReader = null;
        try {
            beanReader = new CsvDozerBeanReader(reader, PREFS);

            final String[] header = beanReader.getHeader(true);

            // set up the field mapping, processors and hints dynamically
            final String[] fieldMapping = new String[header.length];
            final CellProcessor[] processors = new CellProcessor[header.length];
            final Class<?>[] hintTypes = new Class<?>[header.length];
            for( int i = 0; i < header.length; i++ ) {
                if( i < ATT_START_INDEX ) {
                    // normal mappings
                    fieldMapping[i] = header[i];
                    processors[i] = new NotNull();
                } else {
                    // attribute mappings
                    fieldMapping[i] = String.format("attribs[%d]", i - ATT_START_INDEX);
                    processors[i] = new Optional(new ParsePersonAttribute(header));
                    hintTypes[i] = PersonAttribute.class;
                }
            }

            beanReader.configureBeanMapping(Person.class, fieldMapping, hintTypes);

            Person person;
            while( (person = beanReader.read(Person.class, processors)) != null ) {
                System.out.println(String.format("lineNo=%s, rowNo=%s, person=%s", 
                    beanReader.getLineNumber(),
                    beanReader.getRowNumber(), person));
            }

        }
        finally {
            if( beanReader != null ) {
                beanReader.close();
            }
        }
    }

}

Output:

CsvDozerBeanReader with first CSV input:
lineNo=2, rowNo=2, person=Person [firstname=bill, lastname=smith, attribs=[PersonAttribute [key=dog_name, value=fido], PersonAttribute [key=fav_hat, value=porkpie], PersonAttribute [key=fav_color, value=blue]]]
lineNo=3, rowNo=3, person=Person [firstname=james, lastname=smith, attribs=[PersonAttribute [key=dog_name, value=rover], PersonAttribute [key=fav_hat, value=bowler], PersonAttribute [key=fav_color, value=purple]]]
CsvDozerBeanReader with second CSV input:
lineNo=2, rowNo=2, person=Person [firstname=tom, lastname=collins, attribs=[PersonAttribute [key=car_type, value=ford], PersonAttribute [key=floor_number, value=14]]]
lineNo=3, rowNo=3, person=Person [firstname=jim, lastname=jones, attribs=[PersonAttribute [key=car_type, value=toyota], PersonAttribute [key=floor_number, value=120]]]

在整理这个例子时,我发现了一个错误CsvDozerBeanReader在 Super CSV 2.0.1 中,当您组合细胞处理器(例如我在上面的示例中创建的用于解析每个人属性键/值的),具有索引映射,例如:

"firstname","lastname","attribs[0]","attribs[1]"

我刚刚发布了 Super CSV 2.1.0 修复了这个问题。事实证明,Dozer 需要配置一个提示才能使索引映射正常工作。我不是 100% 确定为什么,因为它完全有能力创建每个PersonAttribute并在摆脱自定义单元处理器并使用以下(深度)映射时将其添加到正确的索引:

"firstname","lastname","attribs[0].value","attribs[1].value"

我希望这有帮助 :)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 OpenCSV 将 CSV 解析为多个/嵌套 bean 类型? 的相关文章

随机推荐

  • 检索和修改 XMLHttpRequest 的内容

    我正在为 Firefox Safari Chrome 开发一个浏览器插件 它将拦截页面上的数据 针对正则表达式运行它 然后如果匹配 则重新格式化它 我使用以下方法在页面加载上进行此操作 var meth replaceInElement f
  • 调用未定义的函数 pg_connect() - Wamp

    我想连接到 PostgreSQL 我使用 wamp 64 位 我这里有 阿帕奇2 4 2 PHP 5 4 3 mysql 5 5 24 我还在 php ini 中取消注释 php pgsql 和 php pdo pgsql 但我无法连接 它
  • C# 验证电子邮件地址是否存在

    关于这个帖子关于电子邮件验证 使用 C 你会怎样 发出 VRFY 命令 发出 RCPT 命令 我想您会发现 在很多情况下 这些功能会故意对您撒谎 以击败垃圾邮件发送者 如果有一种方法可以确认电子邮件的真实性 而不是让用户点击验证 或取消订阅
  • atoi 是一个标准函数。但伊托亚不是。为什么?

    为什么会有这样的区别 我遇到了可怕的问题 假设itoa将在stdlib h最后链接了一个自定义版本itoa使用不同的原型 从而产生一些疯狂的错误 那么 为什么不是itoa不是标准函数 它出什么问题了 为什么标准偏向它的孪生兄弟atoi No
  • 为什么对 sysfs 设备属性文件上的“poll”调用没有正确阻止?

    我有一个简单的sysfs 设备属性它显示在我的下面sysfs目录 并调用read返回内核空间变量的值 我想打电话poll在此属性上允许我的用户空间线程阻塞 直到属性显示的值发生变化 我的问题是poll似乎并没有阻止我的属性 它不断返回POL
  • Angular 2:ngFor 完成时回调

    在 Angular 1 中 我编写了一个自定义指令 repeater ready 来使用ng repeat当迭代完成时调用回调方法 if scope last true timeout gt scope parent parent eval
  • 成员函数的部分特化[重复]

    这个问题在这里已经有答案了 可能的重复 部分模板专业化的 无效使用不完整类型 错误 为什么我可以这样做 template
  • Azure WebJobs SDK ServiceBus 连接字符串“AzureWebJobsAzureSBConnection”丢失或为空

    我在 Visual Studio 2015 中创建了一个 Azure Function App 该应用程序具有服务总线队列的触发器 当我在本地运行该应用程序时 它运行得很好 它能够从服务总线队列 通过名为 AzureSBConnection
  • Firebase 推送通知在 iOS 13 上不起作用

    Firebase 推送通知无法在 iOS 13 上运行 但在 iOS 12 4 中运行良好 有什么解决办法吗 编辑 2019 年 10 月 4 日 静默推送通知在 iOS 13 上不起作用 快速修复解决方案 如果您在 iOS 版本 13 2
  • reinterpret_cast 为 void* 不适用于函数指针

    我想将函数指针重新解释为 void 变量 函数指针的类型将是Class void 下面是示例代码 class Test int a int main Test p void a void f reinterpret cast
  • 自引用外键是什么意思?

    我检查了一个遗留数据库 发现了几个引用列自身的外键 引用的列是主键列 ALTER TABLE SchemaName TableName WITH CHECK ADD CONSTRAINT FK TableName TableName FOR
  • pythonnet 在 .net 中嵌入 Python 示例无法加载模块

    我正在尝试运行 Embedding Python in NET 示例https github com pythonnet pythonnet 我已按照故障排除文章在程序基目录中为我的 anaconda 环境设置正确的 PYTHONPATH
  • UIViewController init 方法中隐式解包的选项

    正如文件所说 如果您确定该选项确实包含一个值 您可以通过添加感叹号 来访问其基础值 那么为什么 UIViewController init 方法使用 init nibName nibName String bundle nibBundle
  • Java 中如何从另一个线程中杀死一个线程?

    我从主线程调用两个线程 将它们称为线程 1 和线程 2 当线程 1 停止时 我也想停止或终止线程 2 我该怎么做 我想要的实际输出发生了变化 那就是有一个主类 它也是线程 从主类我调用 thread1 和 thread2 我从主类向 thr
  • 将代码注入到没有自定义属性的所有方法和属性的最简单方法

    周围有很多问题和答案AOP in NETStack Overflow 上经常提到 PostSharp 和其他第三方产品 因此 NET 和 C 世界中似乎有相当多的 AOP 选项 但其中每一个都有其限制 在下载了有前途的 PostSharp
  • 如何查找和调用特定类型的 .Net TypeConverter?

    我想实现一个通用的运行时类型转换函数 它使用 Net TypeConverters 来进行转换 有谁知道如何查找和调用特定类型的 TypeConverter 考虑这个 C 示例 Convert obj to the type specifi
  • Numpy 使用索引数组将一个数组累加到另一个数组中

    我的问题是关于我想使用 numpy 表达的特定数组操作 我有一个浮点数数组w和一个索引数组idx与相同长度w我想总结一下w与相同的idx值并将它们收集在数组中v 作为一个循环 它看起来像这样 for i x in enumerate w v
  • 处理多个 NSURL 连接的最佳方式

    我正在尝试以编程方式创建 xls 工作表 为了填写表格 我正在制作倍数NSURLConnection大约100 现在 我的方法是 建立连接并将数据存储到数组中 该数组有 100 个对象 现在获取第一个对象并调用连接 存储数据 并与数组中的第
  • 与抽象类相比,使用分部类有什么好处?

    我一直在阅读 Programming Microsoft Visual C 2008 The Language 以便更好地了解 C 及其用途 我遇到了我在 ASP Net 的 Page 类中已经遇到过的部分类 在我看来 您似乎可以对抽象类和
  • 使用 OpenCSV 将 CSV 解析为多个/嵌套 bean 类型?

    我有各种 CSV 其中包含一些标准列和一些完全随机的字段 firstname lastname dog name fav hat fav color bill smith fido porkpie blue james smith rove