0

I am trying to persist a person class which has a list of pets to a CSV file and read it back to the corresponding objects. I am able to write it properly but unable to read. Please see below -

Person.java

import java.util.ArrayList;
import java.util.List;

public class Person {

private String name;

private List<Pet> pets;

public Person() {
}

public Person(final String name, final List<Pet> pets) {
    this.name = name;
    this.pets = pets;
}

/**
 * @return the name
 */
public String getName() {
    return name;
}

/**
 * @return the pets
 */
public List<Pet> getPets() {
    return pets;
}

/**
 * @param pets
 *            the pets to set
 */
public void setAddPet(final Pet pet) {
    if (pets == null) {
        pets = new ArrayList<Pet>();
    }
    pets.add(pet);
}

/**
 * @param name
 *            the name to set
 */
public void setName(final String name) {
    this.name = name;
}

/**
 * @param pets
 *            the pets to set
 */
public void setPets(final List<Pet> pets) {
    this.pets = pets;
}

@Override
public String toString() {
    return String.format("Person [name=%s, pets=%s]", name, pets);
}

}

Pet.java

public class Pet {

private String typeOfAnimal;

private String color;

public Pet() {
}

public Pet(final String typeOfAnimal, final String color) {
    this.typeOfAnimal = typeOfAnimal;
    this.color = color;
}

/**
 * @return the color
 */
public String getColor() {
    return color;
}

/**
 * @return the typeOfAnimal
 */
public String getTypeOfAnimal() {
    return typeOfAnimal;
}

/**
 * @param color
 *            the color to set
 */
public void setColor(final String color) {
    this.color = color;
}

/**
 * @param typeOfAnimal
 *            the typeOfAnimal to set
 */
public void setTypeOfAnimal(final String typeOfAnimal) {
    this.typeOfAnimal = typeOfAnimal;
}

@Override
public String toString() {
    return String.format("Pet [typeOfAnimal=%s, color=%s]", typeOfAnimal, color);
}

}

Writer.java

import java.io.FileWriter;
import java.util.Arrays;
import java.util.List;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.dozer.CsvDozerBeanWriter;
import org.supercsv.io.dozer.ICsvDozerBeanWriter;
import org.supercsv.prefs.CsvPreference;

public class Writer {

private static final String[] HEADERS = new String[] { "name", "pets" };

private static final CellProcessor[] processors = new CellProcessor[] { new NotNull(),
    new NotNull() };

private static final String CSV_FILENAME = "C:\\Users\\Desktop\\Test.csv";

public static void writeWithDozerCsvBeanWriter() throws Exception {

    // create the survey responses to write
    final Person person1 = new Person("Dereck", Arrays.asList(new Pet("Dog", 
"Black")));
    final Person person2 =
        new Person("Gavin", Arrays.asList(new Pet("Squirrel", "Brown"), new Pet("Cat",
                "White")));

    final List<Person> people = Arrays.asList(person1, person2);

    ICsvDozerBeanWriter beanWriter = null;
    try {
        beanWriter =
            new CsvDozerBeanWriter(new FileWriter(CSV_FILENAME),
                    CsvPreference.STANDARD_PREFERENCE);

        // configure the mapping from the fields to the CSV columns
        beanWriter.configureBeanMapping(Person.class, HEADERS);

        // write the header
        beanWriter.writeHeader(HEADERS);

        // write the beans
        for (final Person person : people) {
            beanWriter.write(person, processors);
        }

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

Reader.java

import java.io.FileReader;

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 Reader {

private static final String CSV_FILENAME = "C:\\Users\\Desktop\\Test.csv";

/**
 * An example of reading using CsvBeanReader.
 */
public static void readWithCsvBeanReader() throws Exception {

    ICsvBeanReader beanReader = null;
    try {
        beanReader =
            new CsvBeanReader(new FileReader(CSV_FILENAME),
                    CsvPreference.STANDARD_PREFERENCE);

        // the header elements are used to map the values to the bean (names must 
match)
        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 < 1) {
                // normal mappings
                fieldMapping[i] = header[i];
                processors[i] = new NotNull();
            } else {
                // attribute mappings
                fieldMapping[i] = "AddPet";
                processors[i] = new Optional(new ParsePersonPet(header));
            }
        }

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

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

}

ParsePersonPet.java

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


public class ParsePersonPet extends CellProcessorAdaptor {

private final String[] header;

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

@Override
public Object execute(final Object value, final CsvContext context) {

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

    final Pet pet = new Pet();
    pet.setTypeOfAnimal((String) value);
    return pet;
}

}

Currently, its being written to the csv as below -

enter image description here

But when I read it back and print it, it prints as below -

person=Person [name=Dereck, pets=[Pet [typeOfAnimal=[Pet [typeOfAnimal=Dog,  
color=Black]], color=null]]]
person=Person [name=Gavin, pets=[Pet [typeOfAnimal=[Pet [typeOfAnimal=Squirrel,    
color=Brown], Pet [typeOfAnimal=Cat, color=White]], color=null]]]

I know the problem is in the ParsePersonPet.java at

 pet.setTypeOfAnimal((String) value);

But the value seems to be coming back as a String as Pet [typeOfAnimal=Dog, color=Black] Do I have to use String Tokenizer and set the appropriate values? It could be quite tedious right? What do I do here?

Thanks

Update: I got the code to work by changing my ParsePersonPet to below -

import org.supercsv.cellprocessor.CellProcessorAdaptor;
import org.supercsv.util.CsvContext;
public class ParsePersonPet extends CellProcessorAdaptor {

private final String[] header;

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

@Override
public Object execute(final Object value, final CsvContext context) {

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

    final String str = (String) value;

    final Pet pet = new Pet();
    pet.setTypeOfAnimal(getValue("typeOfAnimal", str));
    pet.setColor(getValue("color", str));

    return pet;
}

public String getValue(final String strValueToSearchFor, final String str) {
    if (str.contains(strValueToSearchFor)) {
        final int startIndex =
            str.lastIndexOf(strValueToSearchFor) + strValueToSearchFor.length() + 1;
        int endIndex = str.indexOf(",", startIndex);
        if (endIndex == -1) {
            endIndex = str.indexOf("]", startIndex);
        }
        return str.substring(startIndex, endIndex);
    }
    return null;
}
}

I need to know how to avoid the setAddpet and use setPets instead and also what do I do if I have a map of pets instead of a list.

Thanks

Rtn9
  • 101
  • 10

1 Answers1

0

You have 2 options. Jump to the 'Quick Answer' if you want.

Continue using the standard CsvBeanReader/Writer

As you're aware, CsvBeanReader and CsvBeanWriter can't handle indexed or nested mapping, so there has to be getters/setters on the parent class to access the child class' fields.

After cell processor execution, Super CSV will call toString() on the value then escape any embedded commas, quotes and newlines as necessary. So in your writer, after NotNull() has ensured the List is not null, it's just doing a toString() and escaping. So essentially you end up with a single pets column in your CSV file.

If you wanted a column for each pet, you'd have to have a separate getter (getPet1(), getPet2(), etc) which simply accesses the desired pet in the List. You'd have to know how many pets you want to support - CSV shouldn't have variable columns. I'm imagining you'd actually want 2 columns for each pet - the animal type and color - so you could have different getters ((getPet1Type(), getPet1Color(), etc), or write 2 cell processors (FmtPetType and FmtPetColor) that simply return the type or color.

So your CSV file would look something like:

name,pet1Type,pet1Color,pet2Type,pet2Color
Jo,Dog,Black,Cat,White

Quick Answer

Otherwise, if you want to keep the toString pets list (knowing that your CSV file is harder for other people to parse), you'll need to write a custom cell processor that can take the toString()'d list and parse it into a List of pets again. It's a bit painful, but doable. You wouldn't need the setAddPet() method then, as it could simply use setPets().

Use CsvDozerBeanReader and CsvDozerBeanWriter

I've seen your other question, so I know you've considered used the dozer extension. In this case I'd really recommend it. To get the CSV file I've suggested above you could configure the fieldMapping with:

String[] fieldMapping = {"name","pets[0].typeOfAnimal","pets[0].color",
    "pets[1].typeOfAnimal","pets[1].color"};

And not have to worry about any custom cell processors. If you have a lot of pets, you can simply create the fieldMapping dynamically as I've suggested in your other question.

Community
  • 1
  • 1
James Bassett
  • 9,458
  • 4
  • 35
  • 68
  • Thanks for the answer. I could use the indexed mapping but currently we have some 6000 People with around 100 of them having some 10 pets and inside which we have again multiple nested objects inside which we have again nested objects :( So I think for our application..going with the quick answer is best to avoid soo many columns? Please advice. Also - How do I avoid the setAddpet method and what do I do if the pets were to be a map? We have a combination of lists and maps in our application. – Rtn9 Jun 30 '14 at 18:28