Java to CSV file, SuperCSV Dozer: How to avoid looping for a nested object

空扰寡人 提交于 2019-12-02 15:01:37

问题


I have the below classes..

SurveyResponse.java

import java.util.List;

public class SurveyResponse {

private int age;

private Boolean consentGiven;

private List<Answer> answers;

public SurveyResponse() {
}

public SurveyResponse(final int age, final Boolean consentGiven, final List<Answer>   
answers) {
    this.age = age;
    this.consentGiven = consentGiven;
    this.answers = answers;
}

public int getAge() {
    return age;
}

public List<Answer> getAnswers() {
    return answers;
}

public Boolean getConsentGiven() {
    return consentGiven;
}

public void setAge(final int age) {
    this.age = age;
}

public void setAnswers(final List<Answer> answers) {
    this.answers = answers;
}

public void setConsentGiven(final Boolean consentGiven) {
    this.consentGiven = consentGiven;
}

@Override
public String toString() {
    return String.format("SurveyResponse [age=%s, consentGiven=%s, answers=%s]", age,
        consentGiven, answers);
}

}

Answer.java

public class Answer {
private Integer questionNo;

private String answer;

public Answer() {
}

public Answer(final Integer questionNo, final String answer) {
    this.questionNo = questionNo;
    this.answer = answer;
}

public String getAnswer() {
    return answer;
}

public Integer getQuestionNo() {
    return questionNo;
}

public void setAnswer(final String answer) {
    this.answer = answer;
}

public void setQuestionNo(final Integer questionNo) {
    this.questionNo = questionNo;
}

@Override
public String toString() {
    return String.format("Answer [questionNo=%s, answer=%s]", questionNo, answer);
}

}

and my Main class with the looping which works fine as below -

import java.io.FileWriter;
import java.util.Arrays;
import java.util.List;

import org.supercsv.cellprocessor.FmtBool;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.Token;
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 Main {

 private static final String[] FIELD_MAPPING = new String[] { "age", // simple field  
 mapping
                                                                    // (like  
 CsvBeanReader)
    "consentGiven", // as above
    "answers[0].questionNo", // indexed (first element) + deep mapping
    "answers[0].answer", "answers[1].questionNo", // indexed (second element) + deep   
mapping
    "answers[1].answer", "answers[2].questionNo", "answers[2].answer" };

/**
 * @param args
 * @throws Exception
 */
public static void main(final String[] args) throws Exception {
    writeWithDozerCsvBeanWriter();

}

private static void writeWithDozerCsvBeanWriter() throws Exception {

    final CellProcessor[] processors = new CellProcessor[] { new Token(0, null), // age
        new FmtBool("Y", "N"), // consent
        new NotNull(), // questionNo 1
        new Optional(), // answer 1
        new NotNull(), // questionNo 2
        new Optional(), // answer 2
        new NotNull(), // questionNo 3
        new Optional() }; // answer 4

    // create the survey responses to write
    final SurveyResponse response1 =
        new SurveyResponse(18, true, Arrays.asList(new Answer(1, "Twelve"), new 
Answer(2,
                "Albert Einstein"), new Answer(3, "Big Bang Theory")));
    final SurveyResponse response2 =
        new SurveyResponse(0, true, Arrays.asList(new Answer(1, "Thirteen"), new 
Answer(2,
                "Nikola Tesla"), new Answer(3, "Stargate")));
    final SurveyResponse response3 =
        new SurveyResponse(42, false, Arrays.asList(new Answer(1, null), new Answer(2,
                "Carl Sagan"), new Answer(3, "Star Wars")));
    final List<SurveyResponse> surveyResponses =
        Arrays.asList(response1, response2, response3);

    ICsvDozerBeanWriter beanWriter = null;
    try {
        beanWriter =
            new CsvDozerBeanWriter(new   
 FileWriter("C:\\Users\\Desktop\\Test.csv"),
                    CsvPreference.STANDARD_PREFERENCE);

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

        // write the header
        beanWriter.writeHeader("age", "consentGiven", "questionNo1", "answer1",
            "questionNo2", "answer2", "questionNo3", "answer3");

        // write the beans
        for (final SurveyResponse surveyResponse : surveyResponses) {
            beanWriter.write(surveyResponse, processors);
        }

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

}

I want to avoid the looping in the field mapping and processors arrays because..in our app, we have around 100 to 1000 answers. so I wrote the below which is not working. Could someone shed some light as why the below is not working?

Main2.java

import java.io.FileWriter;
import java.util.Arrays;
import java.util.List;

import org.supercsv.cellprocessor.FmtBool;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.Token;
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 Main2 {

private static final String[] FIELD_MAPPING = new String[] { "age", "consentGiven",
    "answers.questionNo", "answers.answer" };

/**
 * @param args
 * @throws Exception
 */
public static void main(final String[] args) throws Exception {
    writeWithDozerCsvBeanWriter();

}

private static void writeWithDozerCsvBeanWriter() throws Exception {

    final CellProcessor[] processors = new CellProcessor[] { new Token(0, null), // age
        new FmtBool("Y", "N"), // consent
        new NotNull(), // questionNo
        new Optional() // answer
        };

    // create the survey responses to write
    final SurveyResponse response1 =
        new SurveyResponse(18, true, Arrays.asList(new Answer(1, "Twelve"), new 
       Answer(2,
                "Albert Einstein"), new Answer(3, "Big Bang Theory")));
    final SurveyResponse response2 =
        new SurveyResponse(0, true, Arrays.asList(new Answer(1, "Thirteen"), new 
       Answer(2,
                "Nikola Tesla"), new Answer(3, "Stargate")));
    final SurveyResponse response3 =
        new SurveyResponse(42, false, Arrays.asList(new Answer(1, null), new Answer(2,
                "Carl Sagan"), new Answer(3, "Star Wars")));
    final List<SurveyResponse> surveyResponses =
        Arrays.asList(response1, response2, response3);

    ICsvDozerBeanWriter beanWriter = null;
    try {
        beanWriter =
            new CsvDozerBeanWriter(new  
 FileWriter("C:\\Users\\Desktop\\Test.csv"),
                    CsvPreference.STANDARD_PREFERENCE);

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

        // write the header
        beanWriter.writeHeader("age", "consentGiven", "questionNo1", "answer1");

        // write the beans
        for (final SurveyResponse surveyResponse : surveyResponses) {
            beanWriter.write(surveyResponse, processors);
        }

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

}

Here is the error

6 [main] INFO org.dozer.config.GlobalSettings - Trying to find Dozer configuration   
file: dozer.properties

14 [main] WARN org.dozer.config.GlobalSettings - Dozer configuration file not found: 
dozer.properties.  Using defaults for all Dozer global properties.
 15 [main] INFO org.dozer.DozerInitializer - Initializing Dozer. Version: 5.4.0, Thread       
 Name: main

64 [main] INFO org.dozer.jmx.JMXPlatformImpl - Dozer JMX MBean 
[org.dozer.jmx:type=DozerStatisticsController] auto registered with the Platform MBean 
Server

65 [main] INFO org.dozer.jmx.JMXPlatformImpl - Dozer JMX MBean 
[org.dozer.jmx:type=DozerAdminController] auto registered with the Platform MBean 
Server

68 [main] INFO org.dozer.DozerBeanMapper - Initializing a new instance of dozer bean 
mapper.

 156 [main] ERROR org.dozer.MappingProcessor - Field mapping error -->

MapId: null
Type: null
Source parent class: SurveyResponse
Source field name: answers.questionNo
Source field type: null
 Source field value: null
Dest parent class: org.supercsv.io.dozer.CsvDozerBeanData
Dest field name: columns
Dest field type: java.util.List
java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at  
 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.dozer.util.ReflectionUtils.invoke(ReflectionUtils.java:323)
at 
  org.dozer.propertydescriptor.GetterSetterPropertyDescriptor.
  getDeepSrcFieldValue(GetterSetterPropertyDescriptor.java:122)
at org.dozer.propertydescriptor.GetterSetterPropertyDescriptor.
 getPropertyValue(GetterSetterPropertyDescriptor.java:75)
at org.dozer.fieldmap.FieldMap.getSrcFieldValue(FieldMap.java:84)
at org.dozer.MappingProcessor.mapField(MappingProcessor.java:275)
at org.dozer.MappingProcessor.map(MappingProcessor.java:248)
at org.dozer.MappingProcessor.map(MappingProcessor.java:197)
at org.dozer.MappingProcessor.map(MappingProcessor.java:187)
at org.dozer.MappingProcessor.map(MappingProcessor.java:133)
at org.dozer.MappingProcessor.map(MappingProcessor.java:128)
at org.dozer.DozerBeanMapper.map(DozerBeanMapper.java:127)
at org.supercsv.io.dozer.CsvDozerBeanWriter.write(CsvDozerBeanWriter.java:132)
at MainAvoidIndexing.writeWithDozerCsvBeanWriter(MainAvoidIndexing.java:68)
at MainAvoidIndexing.main(MainAvoidIndexing.java:29)

     Exception in thread "main" java.lang.IllegalArgumentException: object is not an 
     instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at 
        sun.reflect.DelegatingMethodAccessorImpl.
   invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.dozer.util.ReflectionUtils.invoke(ReflectionUtils.java:323)
at org.dozer.propertydescriptor.GetterSetterPropertyDescriptor.
   getDeepSrcFieldValue(GetterSetterPropertyDescriptor.java:122)
at  
  org.dozer.propertydescriptor.GetterSetterPropertyDescriptor.
  getPropertyValue(GetterSetterPr  
    opertyDescriptor.java:75)
at org.dozer.fieldmap.FieldMap.getSrcFieldValue(FieldMap.java:84)
at org.dozer.MappingProcessor.mapField(MappingProcessor.java:275)
at org.dozer.MappingProcessor.map(MappingProcessor.java:248)
at org.dozer.MappingProcessor.map(MappingProcessor.java:197)
at org.dozer.MappingProcessor.map(MappingProcessor.java:187)
at org.dozer.MappingProcessor.map(MappingProcessor.java:133)
at org.dozer.MappingProcessor.map(MappingProcessor.java:128)
at org.dozer.DozerBeanMapper.map(DozerBeanMapper.java:127)
at org.supercsv.io.dozer.CsvDozerBeanWriter.write(CsvDozerBeanWriter.java:132)
at MainAvoidIndexing.writeWithDozerCsvBeanWriter(MainAvoidIndexing.java:68)
at MainAvoidIndexing.main(MainAvoidIndexing.java:29)

回答1:


The answers field in SurveyResponse is a List, so you have to use indexed mapping to configure CsvDozerBeanWriter. Indexed mapping is not actually looping - it's just how Dozer knows which element of a List to populate. This is extremely important if you were to ignore a column - it would actually insert a null element for that column.

If you don't use indexed mapping (and just use "answers") then you'd need to configure a cell processor that can take a List<Answer> and convert it to something that can be used as CSV.

Dynamically configuring the field mapping

(Not what you were asking as it turns out, but I'll leave for future reference)

That doesn't mean you have to configure the field mapping with a static String array. You can populate the array dynamically using a simple for-loop. For example,

    String[] fieldMapping = new String[10];
    fieldMapping[0] = "age";
    fieldMapping[1] = "consentGiven";
    int answerStartIndex = 2;
    int answerEndIndex = 8;
    for (int i = answerStartIndex; i <= answerEndIndex; i++){
        fieldMapping[i] = String.format("answers[%s].questionNo", 
            i - answerStartIndex);
    }
    fieldMapping[9] = "age";

Which will give you a fieldMapping of:

[age, consentGiven, answers[0].questionNo, answers[1].questionNo,
 answers[2].questionNo, answers[3].questionNo, answers[4].questionNo,
 answers[5].questionNo, answers[6].questionNo, age]


来源:https://stackoverflow.com/questions/24395851/java-to-csv-file-supercsv-dozer-how-to-avoid-looping-for-a-nested-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!