I have this method in my controller:
@RequestMapping(method = RequestMethod.POST)
InterfaceClass insert(@RequestBody InterfaceClass interfaceClass) {
// Do something
}
The error I am getting is pretty straightforward and self-explanatory:
Can not construct instance of InterfaceClass: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information.
Basically, I need to tell Spring that I have a concrete implementation of InterfaceClass
, ClassImpl
.
I tried:
@JsonRootName("InterfaceClass")
public class ClassImpl implements InterfaceClass {
}
to no extent. I cannot use @JsonTypeInfo
as the parent interface class InterfaceClass
should not be aware of ClassImpl
and they are in different modules. What I have also tried:
Implement InterfaceClass
with abstract AbstractClass
and put:
@JsonDeserialize(as = AbstractClass.class)
on top of InterfaceClass
. Then extend AbstractClass
with ClassImpl
. The error simply becomes:
Can not construct instance of InterfaceClass: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information.
Further tried:
public class ControllerClass<E extends InterfaceClass> {
@RequestMapping(method = RequestMethod.POST)
InterfaceClass insert(@RequestBody E interfaceClass) {
InterfaceClass object = (InterfaceClass) interfaceClass;
}
}
and this results in:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to InterfaceClass
as expected.
I was really expecting Spring Boot to handle component discovery since there is only one concrete implementation of InterfaceClass
or AbstractClass
, which is ClassImpl
in my classpath. Maybe I am doing something wrong? How can I overcome this, without explicitly hinting the InterfaceClass
on where its implementation is (e.g. no @JsonDeserialize
, etc.)?
Solution 1 - Dynamically register subtypes
You can define the subtypes dynamically.
1. On the interface, define a JSON field (@type) to be used as identifier:
2. Add "@type" field to your JSON payload
2. Register the inteface's subtypes dynamically:
4. Specify the "@type" name on your concrete class (optional):
5. Use can now use the interface with @RequestBody:
Solution 2 - Dynamically register a custom Deserializer
If adding a
@type
field isn't possible (or not wanted), you may also define a Custom Deserializer for your interface, which would in fact create aClassImpl
:1. Define a custom deserializer:
2. Dynamically set the custom deserializer:
3. Remove @JsonTypeInfo from the interface: