0

I have a simple POJO that has a Map inside it.

public class Product {
    public Map map;
}

then my csv looks like this:

"mapEntry1","mapEntry2","mapEntry3"

So I created a custom cell processor for parsing those:

 public class MapEntryCellProcessor {
     public Object execute(Object val, CsvContext context) {
         return next.execute(new AbstractMap.SimpleEntry<>("somekey", val), context);
     }
 }

and then I add an entry setter method in my Product:

public void setName(Entry<String, String> entry) {
    if (getName() == null) {
        name = new HashMap<>();
    }
    name.put(entry.getKey(), entry.getValue());
}

Unfortunately this means I have 2 setter methods: one that accepts a map and another one that accepts an entry which doesn't really work for me (I have no control on how the POJOs are generated). Is there any other way I can parse such a CSV and have only setter that accepts a Map in my Product?

user3502260
  • 220
  • 1
  • 2
  • 10
Petar Tahchiev
  • 4,336
  • 4
  • 35
  • 48

1 Answers1

1

It's possible to write a cell processor that collects each column into a map. For example, the following processor allows you to specify the key and the map to add to.

package org.supercsv.example;

import java.util.Map;

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

public class MapCollector extends CellProcessorAdaptor {

    private String key;

    private Map<String, String> map;

    public MapCollector(String key, Map<String, String> map){
        this.key = key;
        this.map = map;
    }

    public MapCollector(String key, Map<String, String> map, 
        CellProcessor next){
        super(next);
        this.key = key;
        this.map = map;
    }

    public Object execute(Object value, CsvContext context) {
        validateInputNotNull(value, context);
        map.put(key, String.valueOf(value));
        return next.execute(map, context);
    }

}

Then assuming your Product bean has a field name of type Map<String,String>, you can use the processor as follows.

package org.supercsv.example;

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

import junit.framework.TestCase;

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

public class MapCollectorTest extends TestCase {

    private static final String CSV = "John,L,Smith\n" + 
        "Sally,P,Jones";

    public void testMapCollector() throws IOException{
        ICsvBeanReader reader = new CsvBeanReader(
        new StringReader(CSV), 
           CsvPreference.STANDARD_PREFERENCE);

        // only need to map the field once, so use nulls
        String[] nameMapping = new String[]{"name", null, null};

        // create processors for each row (otherwise every bean 
        // will contain the same map!)
        Product product;
        while ((product = reader.read(Product.class, 
            nameMapping, createProcessors())) != null){
            System.out.println(product.getName());
        }
    }

    private static CellProcessor[] createProcessors() {
        Map<String, String> nameMap = new HashMap<String, String>();
        final CellProcessor[] processors = new CellProcessor[]{
                new MapCollector("name1", nameMap), 
                new MapCollector("name2", nameMap), 
                new MapCollector("name3", nameMap)};
        return processors;
    }

}

This outputs:

{name3=Smith, name2=L, name1=John}
{name3=Jones, name2=P, name1=Sally}

You'll notice that while the processors execute on all 3 columns, it's only mapped to the bean once (hence the nulls in the nameMapping array).

I've also created the processors each time a row is read, otherwise every bean will be using the same map...which probably isn't what you want ;)

James Bassett
  • 9,458
  • 4
  • 35
  • 68
  • Thanks for your answer, I'll give it a try, although my first impression is that creating the cellprocessors for every row will significantly drop the performance.. I have some CSVs with more than 10000 lines... – Petar Tahchiev Apr 09 '14 at 08:55
  • Do you think it would be OK if the CsvContext also exposed what has been processed so far on this line as well as the name of the current header value. In that case I could create a CsvProcessor like this: `if (csvContext.getLineSoFar(csvContext.getCurrentHeader()) != null) { csvContext.getLineSoFar().put(STRING, STRING); } else { new HashMap(); } ` – Petar Tahchiev Apr 09 '14 at 09:03