Search code examples
javacsvjacksonrestlet

Writing multi-line CSV with JacksonRepresentation


I'm working on a Restlet servlet, which should be capable of serving multiple rows of data in different formats (XML, JSON, CSV for now).

I've settled on Jackson for mapping my POJOs to the different formats, and trying to use JacksonRepresentation (Restlet extension) for this purpose, but I'm having some trouble getting the CSV bit working.

If I use either of the following JacksonRepresentation constructors:

List<MyBean> beansList = getBeans(); // Query database etc.
MyBean[] beansArr = beansList.toArray(new MyBean[beansList.size()]);

// Tried all the following 
JacksonRepresentation<MyBean[]> result = new JacksonRepresentation<MyBean[]>(MediaType.TEXT_CSV, beansArr);

JacksonRepresentation<List<MyBean>> result = new JacksonRepresentation<List<MyBean>>(MediaType.TEXT_CSV, beansList);

JacksonRepresentation<ListWrapper> result = new JacksonRepresentation<ListWrapper>(MediaType.TEXT_CSV, new ListWrapper(beansList));

JacksonRepresentation<ArrayWrapper> result = new JacksonRepresentation<ArrayWrapper>(MediaType.TEXT_CSV, new ArrayWrapper(beansArr));


// Explicit schema shouldn't be necessary, since the beans are already annotated
CsvSchema csvSchema = CsvSchema.builder()
        .addColumn("productcode", ColumnType.STRING)
        .addColumn("quantity", ColumnType.NUMBER)
.build();

result.setCsvSchema(csvSchema);

return result;

I get an exception:

com.fasterxml.jackson.core.JsonGenerationException: Unrecognized column 'productcode': known columns: ]

But if I stick to one object/row:

JacksonRepresentation<MyBean> result = new JacksonRepresentation<MyBean>(MediaType.TEXT_CSV, beansArr[0]);

I get a nice, working CSV file with one row of data.

For JSON the array wrapper approach seems to be the way to do it for multiple objects, but I can't for the life of me figure out how to make a multi-line CSV...

Example bean and wrappers:

@JsonPropertyOrder({ "productcode", "quantity" ... })
public class MyBean {

    private String productcode;

    private float quantity;

    public String getProductcode() {
        return productcode;
    }

    public void setProductcode(String productcode) {
        this.productcode = productcode;
    }

    .
    .
    .
}


public class ListWrapper {

    private List<MyBean> beans;

    public ListWrapper(List<MyBean> beans) {
        setBeans(beans);
    }

    public void setBeans(List<MyBean> beans) {
        this.beans = beans;
    }

    public List<MyBean> getBeans() {
        return beans;
    }
}

public class ArrayWrapper {

    private MyBean[] beans;

    public ArrayWrapper(MyBean[] beans) {
        setBeans(beans);
    }

    public void setBeans(MyBean[] beans) {
        this.beans = beans;
    }

    public MyBean[] beans getBeans() {
        return beans;
    }
}

Solution

  • I think there is a mistake in the current code. The CSV mapper is not correctly computed.

    I've entered an issue (https://github.com/restlet/restlet-framework-java/issues/928) for that, thanks for reporting this issue.

    As a workaround, I suggest you to create the JacksonRepresentation as follow:

    rep = new JacksonRepresentation<MyBean[]>(MediaType.TEXT_CSV, tab) {
        @Override
        protected ObjectWriter createObjectWriter() {
            CsvMapper csvMapper = (CsvMapper) getObjectMapper();
            CsvSchema csvSchema = csvMapper.schemaFor(MyBean.class);
            ObjectWriter result = csvMapper.writer(csvSchema);
            return result;
        }
    };
    

    It works also with List of beans.