I'm using 5.2
I have a base (abstract) bean class with the fields, then a couple of derived classes that only add some methods and/or provide default initialization; I need to change the formatting of a BigDecimal
field, thus I'm using the @CsvNumber
annotation, but I'm forced to put it on the abstract class, I can't do it on the derived one (which has no fields).
I'd like to be able to use those annotations on methods too, like I can do i.e. in Jackson; here's an example, in fact I'm using a mixin (a separate class) to set annotations, without touching the real bean:
Base abstract class:
public abstract class DataValue { private String code; private String group; private String description; public DataValue(ValueType type) { this.type = type; } // ...
Derived class:
public class ComputedValue extends DataValue { public ComputedValue(ValueType type) { super(type); } // ...
This is the one I'm using for Csv writing:
public void getAssetProcessedDataCsv( @PathVariable("assetId") String assetId, @RequestParam("dateFrom") @DateTimeFormat(iso=ISO.DATE_TIME) ZonedDateTime dateFrom, @RequestParam("dateTo") @DateTimeFormat(iso=ISO.DATE_TIME) ZonedDateTime dateTo, HttpServletResponse response) throws Exception { // ... //create a csv writer StatefulBeanToCsv<ComputedValue> writer = new StatefulBeanToCsvBuilder<ComputedValue>(response.getWriter()) .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER) .withSeparator('\t') .withOrderedResults(false) .build(); // ... // data.getContent().getValues() is a Map<String, ComputedValue> //write all users to csv file writer.write(new ArrayList<ComputedValue>(data.getContent().getValues().values())); }
Mixin, this class is never used, just instructs Jackson about how to serialize the data:
public abstract class ComputedValueApi extends ComputedValue { public ComputedValueApi(ValueType type) { super(type); } @JsonIgnore @Override public String getDescription() { // TODO Auto-generated method stub return super.getDescription(); } @JsonIgnore @Override public String getGroup() { // TODO Auto-generated method stub return super.getGroup(); } // ...
This is how the mixin is configured to the mapper:
jacksonConverter.getObjectMapper().addMixIn(ComputedValue.class, ComputedValueApi.class);
Oh, and thanks for this library, makes me turn a processed result data structure into a Csv in like 10 lines of code.
Ummmmmmm - I am going to let Andrew have the final say on this because he knows the annotations way better than I do. Because he wrote them <bg>. Andrew - sorry for throwing you under the bus.</bg>
That said I do want to document two concerns.
First is we would need to do the setter as well as the getter. Very minor as the annotations have a value and writeValue but I wanted to note it for fairness.
Secondly is that we would have to setup and maintain an order of precedence. Not only for the annotation of the getter overriding the annotation of the field but also the annotation of the child class overriding the annotation of the parent class - and making sure the correct annotation is used when a class is cast to its parent class. My gut tells me this would make for some very hairy code.
For the second point I thought about us getting around this by stating only one annotation allowed and thowing an error if there is an annotation on both the getter and field or getter and overriding getter (Use case: I am using domain classes from a third party and they do not have any annotations defined and would like to create a child class with the annotations on getter and setters instead of creating a duplicate data class and a utility class to translate between the two). But that would kill the use case: I am using a domain class from a third party that has a csv annotation that I would like to override for a different format.
Scott :(
I don't believe there would be any problems with casting, overriding, and so on. Java takes care of all of that.
However, this looks like it would be a major pain. I will have to think long and hard about this.
Question: would you be happy with straight descendancy, or would you prefer mix-in classes as you demontrated, or would you have to have both?
Thank you for both your replies.
AFAITC the mixin is the way that lets me serialize (to CSV, to JSON, etc) the existing bean provided by the third party or other source as-is, without touching it.
With subclassing, instead, I would have to recreate each instance, because I cannot directly cast the parent instance to the one from my subclass which holds the CSV-related annotations.
And subclassing forcefully requires the ability to annotate (at least) the getters.
In my Jackson example, I'm using subclassing to get the same names for convenience of the fields / getters, but I'm pretty sure Jackson would accept an entirely different class, provided it has the exact names for the fields I want to mix-in.
The ability to annotate the getters and setters has another advantage: it allows handling serialization / deserialization in different ways. E.g. I recently discovered that by annotating the getters and setters instead of the fields, Jackson allows using different names when deserializing and serializing the same object.
Thus I can populate the following object from a source like
{"class": "prime"}
but send it out like{"className": "prime"}
:I can't delve into this more, I may only say that if I should give a priority I would suggest to think about implementing both (so that architectural choices do not pose a problem later on) but then start implementing the easiest of the two.
We're still considering whether or not we're interested in doing something like this. The way we see it, the fundamental need here is to populate beans that the developer has no source control over (or perhaps does not wish to annotate for other reasons). I'm curious: would an external XML configuration file that tells opencsv how to bind to a bean be good for you?
I am no longer actively developing in the project.
Last edit: Andrew Rucker Jones 2022-10-21