如何从CSV文件生成Java类

4

我有多个包含200到300列的csv文件,我需要根据一对一的映射关系(从列到Java类字段)创建pojos(Plain Old Java Object, 简单Java对象)。无论是否推荐,都需要实现此操作。

如果你知道任何工具或自动完成此操作的方法,请提供帮助。

所以,你有一个包含数千行和几百列的csv文件,第一行包含列标题。基于第一行(列的标题),我需要创建一个Java类,其中包含那些作为类字段的列标题。不考虑实际数据,只需使用这些字段创建一个Java类即可。

关于这个帖子有一个相关问题,但这是3年前的问题,所以我猜已经过时了。


你可以使用Javassist在运行时生成类。 - Binkan Salaryman
一个CSV列应该生成一个类字段,所以255个CSV列应该可以得到一个有255个字段的类 :) - aurelius
我询问了映射,并假设您的意思是将行对象引用分配给行对象的可能性... - Binkan Salaryman
你有一个包含数千行和数百列的csv文件,第一行包含列的标题。所以根据第一行(列标题)需要创建一个Java类,其中包含这些标题作为类字段。不必考虑实际数据。我只需要一个具有这些字段的Java类。 - aurelius
2
我必须问一下 - 这样生成的类有什么用处呢?我唯一能想到的可能用途就是混淆和嵌入CSV数据... - tucuxi
显示剩余3条评论
3个回答

8
您可以使用Javassist在运行时生成类: 代码:
public static void main(String[] args) throws Exception {
    String[] fieldNames = null;
    Class<?> rowObjectClass = null;
    try(BufferedReader stream = new BufferedReader(new InputStreamReader(Program.class.getResourceAsStream("file.csv")))) {
        while(true) {
            String line = stream.readLine();
            if(line == null) {
                break;
            }
            if(line.isEmpty() || line.startsWith("#")) {
                continue;
            }
            if(rowObjectClass == null) {
                fieldNames = line.split(",");
                rowObjectClass = buildCSVClass(fieldNames);
            } else {
                String[] values = line.split(",");
                Object rowObject = rowObjectClass.newInstance();
                for (int i = 0; i < fieldNames.length; i++) {
                    Field f = rowObjectClass.getDeclaredField(fieldNames[i]);
                    f.setAccessible(true);
                    f.set(rowObject, values[i]);

                }
                System.out.println(reflectToString(rowObject));
            }
        }
    }
}

private static int counter = 0;
public static Class<?> buildCSVClass(String[] fieldNames) throws CannotCompileException, NotFoundException {
    ClassPool pool = ClassPool.getDefault();
    CtClass result = pool.makeClass("CSV_CLASS$" + (counter++));
    ClassFile classFile = result.getClassFile();
    ConstPool constPool = classFile.getConstPool();
    classFile.setSuperclass(Object.class.getName());
    for (String fieldName : fieldNames) {
        CtField field = new CtField(ClassPool.getDefault().get(String.class.getName()), fieldName, result);
        result.addField(field);
    }
    classFile.setVersionToJava5();
    return result.toClass();
}

public static String reflectToString(Object value) throws IllegalAccessException {
    StringBuilder result = new StringBuilder(value.getClass().getName());
    result.append("@").append(System.identityHashCode(value)).append(" {");
    for (Field f : value.getClass().getDeclaredFields()) {
        f.setAccessible(true);
        result.append("\n\t").append(f.getName()).append(" = ").append(f.get(value)).append(", ");
    }
    result.delete(result.length()-2, result.length());
    return result.append("\n}").toString();
}


资源:

file.csv(类路径):

############
foo,bar
############
hello,world
cafe,babe


Output:

CSV_CLASS$0@1324706137 {
    foo = hello, 
    bar = world
}
CSV_CLASS$0@1373076110 {
    foo = cafe, 
    bar = babe
}

2
我也一直在做同样的事情...现在可以停了 x) - romfret
我对你的解决方案很感兴趣 :) - Binkan Salaryman
似乎是我的答案,我会立即尝试。 - aurelius
请注意,资源文件 file.csv 必须在同一目录下,并在其前面添加一个 \。因此,请在 "/file.csv" 上调用该方法。我无法在代码中进行更正,因为编辑至少需要更改六个字符。 - So S

1
根据我的理解,您正在尝试读取具有大量列的csv文件,并将其视为数据库表格。我的解决方案如下:
  • 使用csvjdbc查询数据列
这可以通过使用文件作为数据源和csvjdbc驱动程序,并使用元数据来检索所有列来完成。
  • 使用提供程序创建运行时POJO类
这可以完成,参考链接在 这里

1
这个版本不使用任何花哨的类生成代码,而是输出Java源文件(或者更准确地说,一个类文件,其中每个非标题行都有一个静态公共内部子类)。
对于以下输入(借鉴自Binkan):
############
foo,bar
############
he"l\nl"o,world
cafe,babe

输出将是:
public class Out {
    public static class Row1 {
        public String foo = "he\"l\\nl\"o";
        public String bar = "world";
    }
    public static class Row2 {
        public String foo = "cafe";
        public String bar = "babe";
    }
}

以下是代码,部分参考了Binkan的逐行读取:

public class T {

    public static void classesFromRows(String fileName, PrintWriter out, String classNamePrefix) throws Exception{
        try(BufferedReader stream = new BufferedReader(new FileReader(fileName))) {
            String line = null;
            String[] fields = null;
            int rowNum = 0;
            while ((line = stream.readLine()) != null) {
                if (line.isEmpty() || line.startsWith("#")) {
                    // do nothing
                } else if (fields == null) {
                    fields = line.split(",");
                } else {
                    rowNum ++;
                    String[] values = line.split(",");
                    out.println("\tpublic static class " + classNamePrefix + rowNum + " {");
                    for (int i=0; i<fields.length; i++) {
                        out.println("\t\tpublic String " + fields[i] + " = \"" 
                            + StringEscapeUtils.escapeJava(values[i]) + "\";");
                    }
                    out.println("\t}");
                }           
            }
        }
    }

    // args[0] = input csv; args[1] = output file
    public static void main(String[] args) throws Exception {       
        File outputFile = new File(args[1]);
        String outputClass = outputFile.getName().replace(".java", "");
        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(outputFile)));
        // missing: add a package here, if you want one
        out.println("public class " + outputClass + " {");
        classesFromRows(args[0], out, "Row");
        out.println("}");
        out.close();
    }
}

我选择将所有数据视为字符串(好处是,我正在使用StringEscapeUtils正确转义它们);通过一些额外的代码,你可以指定其他类型。


我认为这并不意味着要将所有行放在单独的类中,而不是在第一行中描述的字段中。 - Binkan Salaryman
这是可能的;我的代码很容易修复以处理它。然而,我认为上述代码或修订版本对任何人都没有太大用处,除了证明编写原始源代码生成器有多么容易。 - tucuxi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接