Reputation: 2669
ObjectMapper
's readValue(InputStream in, Class<T> valueType)
function requires the Class. But how do I use it if the class I am passing internally, is having some Interface as data member.
although I can understand the reason behind this exception, as Jackson is not getting the concrete class of the internal Interface of the passed class, but my question is how to resolve it? how do I deserialize it then? The class I am trying to deserialize is:
class BaseMetricImpl<N> implements Metric<N> {
protected MetricValueDescriptor descriptor;
}
Here MetricValueDescriptor
is an interface, so this gives me following error : -
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of MetricValueDescriptor, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
at [Source: java.io.ByteArrayInputStream@2ede2c9f; line: 1, column: 2] (through reference chain: SingleValueMetricImpl["descriptor"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:624)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:115)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:375)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:308)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2793)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1989)
Upvotes: 50
Views: 99294
Reputation: 1109
You don't need to alter the code, you can set it programmatically on the mapper:
static setup() {
final var simpleModule = new SimpleModule()
.addAbstractTypeMapping(<Interface>.class, <Implementation>.class);
objMapper = new ObjectMapper()
.registerModule(new Jdk8Module()) // You probably want this as well
.registerModule(simpleModule);
}
Upvotes: 14
Reputation: 35415
Jackson obviously cannot construct the MetricValueDescriptor
object since it is an interface. You will need to have additional information in your json and in your ObjectMapper to tell jackson how to construct an object out of it. Here is one way to do it, assuming MVDImpl
is a concrete class which implements MetricValueDescriptor
:
You can tell Jackson the required type information through a field in the json itself, say "type"
. To do this, you need to use JsonTypeInfo
and JsonSubTypes
annotations in your interface. For example,
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@Type(value = MVDImpl.class, name = "mvdimpl") })
interface MetricValueDescriptor
{
...
}
You will need to add a "type":"mvdimpl"
field in your json as well.
I was going to point you to the official doc for more info, but then I found an excellent blog covering this topic - Deserialize JSON with Jackson. It covers this topic pretty comprehensively and with examples. So you should definitely read it if you need more customisation.
Upvotes: 74
Reputation: 13500
I see it going one of two ways, but they both require you manually create a concrete class that implements your interface.
@JsonSubTypes
. This works if you can introduce a type field or something else to trigger which implementation to use.@JsonDeserialize
to tell jackson what concrete class it uses by default.@JsonDeserialize(as = MVDImpl.class)
interface MetricValueDescriptor
{
...
}
Here's a more thorough explanation: https://zenidas.wordpress.com/recipes/jackson-deserialization-of-interfaces/
And the docs: https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/annotation/JsonDeserialize.html
Upvotes: 13