Writing generic POJO to CSV transformer

14.8k views Asked by At

My use case was to write a generic CSV transformer, which should be able to convert any Java POJO to CSV string.

My Implementation :

public <T> List<String> convertToString(List<T> objectList) {

        List<String> stringList = new ArrayList<>();
        char delimiter = ',';
        char quote = '"';
        String lineSep = "\n";

        CsvMapper mapper = new CsvMapper();
        CsvSchema schema = mapper.schemaFor(!HOW_TO!);

        for (T object : objectList) {

            try {
                String csv = mapper.writer(schema
                        .withColumnSeparator(delimiter)
                        .withQuoteChar(quote)
                        .withLineSeparator(lineSep)).writeValueAsString(object);

            } catch (JsonProcessingException e) {

                System.out.println(e);
            }
        }

        return stringList;
}

I was using Jackson-dataformat-csv library, but I'm stuck with !HOW_TO! part, ie How to extract the .class of the object from the objectList. I was studying and came across Type Erasure, So I think it is somehow not possible other than giving the .class as parameter to my function. But I'm also extracting this object list from generic entity using Java Reflection, so I can't have the option to provide the .class params.

Is there a workaround for this?

OR

Any other approaches/libraries where I can convert a generic List<T> objectList to List<String> csvList with functionality of adding delimiters, quote characters, line separators etc.

Thanks!

3

There are 3 answers

0
Mark Adelsberger On

It seems this problem is just harder than most people would like it to be as a result of how Java does generics. Bruno's answer shows options that might work if you can make certain assumptions or can structure your code a certain way.

Another option that should work for your case can be found by way of the answers to this other question: How to get a class instance of generics type T

In there you'll find a link to an article: http://blog.xebia.com/acessing-generic-types-at-runtime-in-java/

This describes how to use the ParameterizedType of an object's superclass. You can apply that to your List object and hopefully it will work for you. This only may luckily work in this case, because you're taking as a parameter an object with a superclass whose type parameters match what you need.

Truly in general, we can't rely on knowing the type parameters at runtime. We can at best maybe use type tokens (parameter of type Class<T>)

4
Bruno Delor On

There is a simple option. I've added some lines to your code to show it :

public <T> List<String> convertToString(List<T> objectList) {

    if(objectList.isEmpty())
        return Collections.emptyList();

    T entry = objectList.get(0);

    List<String> stringList = new ArrayList<>();
    char delimiter = ',';
    char quote = '"';
    String lineSep = "\n";

    CsvMapper mapper = new CsvMapper();
    CsvSchema schema = mapper.schemaFor(entry.getClass());

    for (T object : objectList) {

        try {
            String csv = mapper.writer(schema
                    .withColumnSeparator(delimiter)
                    .withQuoteChar(quote)
                    .withLineSeparator(lineSep)).writeValueAsString(object);

            stringList.add(csv);
        } catch (JsonProcessingException e) {
            System.out.println(e);
        }
    }

    return stringList;
}

The trick is to get one of the elements of the list. In order to avoid crashs I've added a little data integrity test at the beginning that return an unmodifiable empty list in the case there are no items in the input list. Then you retrieve an instance of your Object and use that to get the class.

Alternatively if the convertToString method is in a parametrized class you can do that in a slightly different way

public class GenericClass<T> {

 private final Class<T> type;

 public GenericClass(Class<T> type) {
      this.type = type;
 }

 public Class<T> getMyType() {
     return this.type;
 }
}

This solution allow you to get the class of T. I don't think you'll need it for this question but it might comes in handy.

0
Devendra Dora On

I have created a CSVUtil Class similar to below which uses java reflection.

Example to use below CSVUtil Assuming POJO Student ,

List<Student> StudentList = new ArrayList<Student>();
String StudentCSV = CSVUtil.toCSV(StudentList,' ',false);


import java.lang.reflect.Field;
import java.util.List;
import java.util.logging.Logger;

CSVUtil class

public class CSVUtil {
    private static final Logger LOGGER = Logger.getLogger(CSVUtil.class .getName());
    private final static char DEFAULT_SEPARATOR = ' ';

    public static String toCSV(List<?> objectList, char separator, boolean displayHeader) {
        
        StringBuilder result =new StringBuilder();  
        if (objectList.size() == 0) {
            return result.toString();
        }   
       
        if(displayHeader){
            result.append(getHeaders(objectList.get(0),separator)); 
            result.append("\n");
        }
        
        for (Object obj : objectList) {
            result.append(addObjectRow(obj, separator)).append("\n");
        }
        
        return result.toString();
    }
    
    public static String getHeaders(Object obj,char separator) {
        StringBuilder resultHeader = new StringBuilder(); 
        boolean firstField = true;
        Field fields[] = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            String value;
            try {
                value = field.getName();
                
                if(firstField){
                     resultHeader.append(value);
                     firstField = false;
                 }
                 else{
                    resultHeader.append(separator).append(value);
                 }
                field.setAccessible(false);
            } catch (IllegalArgumentException  e) {
                LOGGER.severe(e.toString());
            }
        }           
      return resultHeader.toString();
        
    }
    

    public static String addObjectRow(Object obj, char separator) {
        
        StringBuilder csvRow =new StringBuilder();  
        Field fields[] = obj.getClass().getDeclaredFields();
        boolean firstField = true;
        for (Field field : fields) {
            field.setAccessible(true);
            Object value;
            try {
                value = field.get(obj);
                if(value == null)
                    value = "";
                if(firstField){
                    csvRow.append(value);
                    firstField = false;
                }
                else{
                    csvRow.append(separator).append(value);
                }
                field.setAccessible(false);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                LOGGER.severe(e.toString());
            }
        }
        return csvRow.toString();
    }
}