3

I'm having an issue with building REST architecture for some legacy code. Jackson ObjectMapper is unable to map my custom object to legacy object, because of 'enums' that really are classes with static final fields.

I tried implementing custom converters/deserializers with no success

In the legacy system there are enums that look like this:

public final class LegacyEnum extends LegacyEnumSuperclass {

    public static final LegacyEnum VALUE = new LegacyEnum("1");

I'm receiving values of these 'enums' as Strings, that I convert to legacy enum values (custom deserializer) and set them in my custom class (I need it because I'm using jackson annotations, and I have no access or permission to modify legacy code) and this part works nicely. When I try to map my custom object to legacy object with

objectMapper.convertValue(myCustomObject, LegacyObjectContainingEnums.class); 

I get an exception:

Can not construct instance of LegacyEnum: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)

The LegacyEnum class has a private constructor, and LegacyEnumSuperclass has a similar protected constructor so I cannot access them (neither can ObjectMapper). I have tried implementing a custom converter, that would skip the 'create new object' part of ObjectMapper mapping, and I also tried to reuse my custom deserializer. I ran into multiple issues and achieved no success.

The most annoying part is, that when I use ModelMapper library it works like a charm (it probably just sets a value in the legacy object, no need to create new LegacyEnum instance like ObjectMapper!) but I'm trying to resolve that issue without adding new dependencies.

Any ideas?

mklepa
  • 211
  • 3
  • 7
  • Do I have a deja vu or did you ask the same question yesterday? – GhostCat Feb 04 '19 at 08:37
  • Nope, I did but got no answers so I deleted the previous question and asked it again. You're a vigilant mate, I have to admit – mklepa Feb 04 '19 at 08:39
  • A) interesting questions stick in memory B) please don't do that. When your question didn't get much attention ... then maybe due to how it is was written. This one reads better. But again: deleting questions and just asking again is not an appreciated practice here.The better way: gain a bit of reputation, and then add a **bounty** to your question. That typically attracts people quite more. Finally: it might be that there is no solution here. There is a reason why there are different frameworks: because they simply have different pros and cons. Maybe this cant be solved with jackson! – GhostCat Feb 04 '19 at 08:45
  • Thank you for advice, I'm new to asking questions, as you can probably tell so I'll make sure to keep your tips in mind. Have a nice day :) – mklepa Feb 04 '19 at 08:47
  • @mklepa how are your custom de- and serializers registered? Because if you instantiated `ObjectMapper` yourself they may not be registered, which leads Jackson to doing it the "standard" way (trying to access a constructor). – Lino Feb 04 '19 at 09:01
  • 1
    Lino is correct here, if you want to go for real, you should create a real [mcve]. Not all your code, but an example that is complete (compiles and runs) and shows your error. – GhostCat Feb 04 '19 at 09:02

1 Answers1

0

I resolved the issue by using MixIn and custom deserializer, like this:

public abstract class LegacyClassMixIn 
    @JsonDeserialize(using = LegacyEnumDeserializer.class)
    abstract LegacyEnum getLegacyEnum();

Deserializer:

public class LegacyEnumDeserializer extends JsonDeserializer<LegacyEnumSuperclass> implements ContextualDeserializer {

private JavaType valueType;

@Override
public JsonDeserializer createContextual(DeserializationContext context, BeanProperty property) {
    JavaType wrapperType = property.getType();
    LegacyEnumDeserializer deserializer = new LegacyEnumDeserializer();
    deserializer.valueType = wrapperType;

    return deserializer;
}

@Override
public LegacyEnumSuperclass deserialize(JsonParser parser, DeserializationContext context) throws IOException {
    return LegacyEnumSuperclass.getEnum(valueType.getRawClass(), parser.readValueAs(String.class));
}

valueType.getRawClass() returns LegacyEnum.class, that way I can use one deserializer for all of the 'enums' that inherit LegacyEnumSuperclass class. getEnum is a custom method, from the legacy code.

Registering MixIn in ObjectMapper Spring configuration:

@Configuration
public class ObjectMapperConfig {

    public ObjectMapperConfig(ObjectMapper objectMapper) {
        objectMapper.addMixIn(LegacyClass.class, LegacyClassMixIn.class);
    }
}

That way I can use LegacyClass as a parameter in a Controller method. Thanks for clues.

mklepa
  • 211
  • 3
  • 7