1

I came across a problem with Generics and Jackson recently and ended up with not using it. I have an interface MonetaryType:

public interface MonetaryType implements Serializable {}

which is implemented by multiple enums like:

public enum IncomeType implements MonetaryType {
    FULL_TIME_SALARY,
    PART_TIME_SALARY,
    CHILD_BENEFIT
}

public enum ExpenseType implements MonetaryType {
    HEAT,
    CONDO_FEES,
    ELECTRICITY
}

I created a Generic Class:

public MonetaryValidation<T extends MonetaryType> {
    
   private T monetaryType;
   private boolean isPassed;
   private String message;

   // Getters and Setters

}

This object is not deserialized by Jackson library. Meaning that if any Spring REST endpoints are called while the payload contains MonetaryValidation object, it throws below exception:

java.lang.IllegalArgumentException: Cannot construct instance of **.enumeration.MonetaryType (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

I do not want to solve the issue with Jackson polymorphic deserialization approach since it requires the client to pass an extra flag specifying the concrete implementation of the interface or abstract class, as far as I understood.

Unfortunately I ended up creating multiple sub classes of non-generic MonetaryValidation (one subclass per each MonetaryType subclass), which I know it is not a decent solution.

It is much appreciated if you could help me out to understand where the problem is and whether there is an approach to use @JsonSubTypes while passing an extra field is not needed.

Ali K. Nouri
  • 495
  • 5
  • 18

1 Answers1

0

There is an idea, try accept monetaryType as the String type parameter, and you can custom converter in the Generic class for handling the generic type field, such as:

        public void setMonetaryType(String monetaryType) {
            Optional<IncomeType> incomeType = Arrays.stream(IncomeType.values()).filter(i -> i.name().equals(monetaryType)).findFirst();

            incomeType.ifPresent(i -> {
                this.monetaryType = (T)i;
            });

            Optional<ExpenseType> expenseType = Arrays.stream(ExpenseType.values()).filter(i -> i.name().equals(monetaryType)).findFirst();

            expenseType.ifPresent(i -> {
                this.monetaryType = (T)i;
            });
        }

I think this is a simply way to achieve other than using JsonSubTypes or custom Converters, since it's really a generic parameter.

chengpohi
  • 14,064
  • 1
  • 24
  • 42
  • Thanks for your response, This could be an expensive approach since per each Rest call it iterates over N loops and instantiates N Optional objects, where N is the number of MonetaryType sub-classes. However, it is a clean approach, it encapsulates all the dirty work in one setter method. – Ali K. Nouri Oct 24 '20 at 22:43
  • @AliK.Nouri but I think you can define an abstract class to encapsulates the common converter if it's possible. – chengpohi Oct 25 '20 at 05:03
  • Yes but that needs to change monetaryType type to String. I'm trying to find a way not to do that. – Ali K. Nouri Oct 26 '20 at 00:41