How to deserialize interface fields using Jackson's objectMapper? - java

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)

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.

I see it going one of two ways, but they both require you manually create a concrete class that implements your interface.
Use #Hari Menon's answer and use #JsonSubTypes. This works if you can introduce a type field or something else to trigger which implementation to use.
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

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);
}

Related

Java Polymorphic Json Deserialization

I am using jackson as part of serializing and deserializing in my project (Spring Java).
In normal scenarios where I have interface(contract) acting as field in POJO,
then I use #JsonTypeInfo and #JsonSubTypes to achieve deserialization in polymorphic cases.
But, right now, I have scenariio something like this:
public class classA {
private contractA fieldA;
//constructor and getter-setters.
}
then,
public interface contractA {
}
and finally,
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(/* concrete-class1 as name-value */),
#JsonSubTypes.Type(/* concrete-class2 as name-value */),
})
public interface contractB extends contractA {
//contract methods.
}
Now, when classA is passed as controller request body and I pass fieldA as concrete-class1 or concrete-class2,
JsonSubTypes are not being used by jackson to deserialize into one of them.
The reason why I did this and had two contracts is due to package dependencies. contractB
is in different package as of contractA's.
How can I configure on contractA using jackson that this class has its JsonSubTypeInfo specified in its subclasses.
Or, any other libraries or approaches are also welcomed.
Thank you !
This problem is later on solved by introducing our own custon JsonTypeInfo.
When the application is under deployment, we fetch all subclasses which is present in the JsonTypeInfo annotation (jackson like custom annotaion) and maintain a data-structure, that will be used while serializing and deserializing. This process is somewhat similar to the Jackson one (in addition to lookup for nested hierarches as well).

#JsonTypeInfo with Spring Data Rest results in serialization error

I am currently trying a scenario with EclipseLink, spring-data-jpa and spring-data-rest where I have Embeddable classes with inheritance.
The scenario is rather simple: A Parent contains a value that can ether be a PercentageValue or an AbsoluteValue.
The mapping:
The Parent holds an embeddable value:
#Entity
public class Parent {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Embedded
private Value value;
}
Value is the abstract superclass for the different values
#Embeddable
#Customizer(Customizers.ValueCustomizer.class)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#Type(name="PERCENTAGE", value=PercentageValue.class),
#Type(name="ABSOLUTE", value=AbsoluteValue.class)})
public abstract class Value {}
PercentageValue is an example for a concrete Value implementation
#Embeddable
#Customizer(Customizers.PercentageValueCustomizer.class)
#JsonSerialize(as = PercentageValue.class)
public class PercentageValue extends Value {
private BigDecimal percentageValue;
}
Using EclipseLink customizers I can get the inheritance with embeddables to work but spring-data-rest does not seem to be able to serialize the value objects because of the type information.
A GETrequest on the parents resource results in the following exception:
.w.s.m.s.DefaultHandlerExceptionResolver : Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Type id handling not implemented for type java.lang.Object (by serializer of type org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$NestedEntitySerializer) (through reference chain: org.springframework.data.rest.webmvc.json.["content"]->com.example.Parent["value"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type java.lang.Object (by serializer of type org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$NestedEntitySerializer) (through reference chain: org.springframework.data.rest.webmvc.json.["content"]->com.example.Parent["value"])
It seems that NestedEntitySerializer is not implementing com.fasterxml.jackson.databind.JsonSerializer#serializeWithType falling back to the standard implementation that just throws an exception.
If I remove the #JsonTypeInfo annotation the serialization works but of course the POST fails because Jackson is lacking the type information for proper deserialization.
Any thoughts on that? Is there a way to make the serialization work with JsonTypeInfo?
The complete project is available on GitHub.
Spring Data REST has a number of documented limitations when it comes to supporting Jackson's #JsonTypeInfo when serializing data to the REST output.
Currently, it appears that #JsonTypeInfo(include=JsonTypeInfo.As.EXISTING_PROPERTY) works (spring-data-rest#1242), but the other JsonTypeInfo.As types do not (spring-data-rest#1269).
It should be possible to migrate your model to use JsonTypeInfo.As.EXISTING_PROPERTY instead of JsonTypeInfo.As.PROPERTY.

How to deserialize abstract classes in jackson

I am trying to deserialize a JSON object to a Java object using jackson. The json was created using the jackson library itself by another developer and is provided to me. The Object graph has a variable of Type guava's ImmutableMap which is an abstract class.Hence jackson throws me the following exception when I try to deSerialize. I am not the owner of the myJava class and hence couldn't make any changes to it like changing the type or adding annotations by writing a custom deserializer.
org.codehaus.jackson.map.JsonMappingException: Can not find a
deserializer for non-concrete Map type [map type; class
com.google.common.collect.ImmutableMap, [simple type, class
com.walmart.services.common.enums.ShipMethod] -> [simple type, class
com.walmart.services.shipprice.api.DetailedShipPrice]]
Thanks
When dealing with Guava types, you will want to use Guava datatype module (https://github.com/FasterXML/jackson-datatype-guava). It is not a problem of just having abstract type; but Guava also uses builder-style instead of constructors.

Jackson equivalent to #XmlSeeAlso

I am writing a RESTful web service using Java and Jersey, where the service will accept either XML or JSON inputs. Jackson is used as the JSON deserializer, and integrated into the Jersey config.
One of the endpoints is a POST request to a URL, where the content can be one of several different Java classes, and there is a common base class. These classes - with XML annotations - are:
#XmlRootElement(name = "action")
#XmlAccessorType(XmlAccessType.NONE)
#XmlSeeAlso({ FirstAction.class, SecondAction.class, ThirdAction.class })
public abstract class BaseAction {
}
#XmlRootElement(name = "first-action")
#XmlAccessorType(XmlAccessType.NONE)
public class FirstAction extends BaseAction implements Serializable {
}
// Likewise for SecondAction, ThirdAction
In my resource I can declare a method like:
#POST
#Path("/{id}/action")
public Response invokeAction(#PathParam("id") String id, BaseAction action) {...}
Then I can POST an XML fragment that looks like <firstAction/> and my method will be invoked with a FirstAction instance. So far so good.
Where I'm struggling is getting the JSON deserialization to work as seamlessly as the XML deserialization. Where the #XmlSeeAlso annotation was critical to get the XML deserialization working properly, it seemed that the equivalent for JSON was #JsonSubTypes. So I annotated the classes like this:
// XML annotations removed for brevity, but they are present as in the previous code snippet
#JsonSubTypes({ #JsonSubTypes.Type(name = "first-action", value = FirstAction.class),
#JsonSubTypes.Type(name = "second-action", value = SecondAction.class),
#JsonSubTypes.Type(name = "third-action", value = ThirdAction.class) })
public abstract class BaseAction {
}
#JsonRootName("first-action")
public class FirstAction extends BaseAction implements Serializable {
}
// Likewise for SecondAction, ThirdAction
I then feed it my test input: { "first-action": null } but all I can get is:
"org.codehaus.jackson.map.JsonMappingException: Root name 'first-action' does not match expected ('action') for type [simple type, class com.alu.openstack.domain.compute.server.actions.BaseAction]"
Unfortunately since I'm trying to be compatible with someone else's API I can't change my sample input - { "first-action": null } has to work, and deliver to my method an object of class FirstAction. (The action doesn't have any fields, which is why null shouldn't be a problem - it's the type of the class that's important).
What's the correct way to have the JSON deserialization work in the same way as the XML deserialization already is?
If you are using Jackson, you are looking for #JsonTypeInfo and #Type . Please see here for more information
JSON does not work the way XML does, so the solution is not identical.
What you need to use is (like the other answer said), #JsonTypeInfo. That only triggers inclusion and use of the type identifier. If so, then '#JsonSubTypes` will be of use in deserialization.
The reason this indicator must be used is simple: if you have more than one alternative type to deserialize to, there must be something to differentiate.
Note, too, that this does NOT have to be a property -- while most users choose "As.PROPERTY" inclusion, it is not (IMO) the best way. "WRAPPER_OBJECT" may be what you are looking for, as it adds an extra intermediate JSON property, which is somewhat similar to what XML does.
I investigated the use of #JsonTypeInfo but ran into problems because I could not alter the input format. The parser absolutely had to be able to handle input { "first-action":null }. This ruled out the possibility of adding an #type or #class property. Using a wrapper object may have worked, but it choked on the null payload.
A crucial point was that I was using the UNWRAP_ROOT_PROPERTY configuration option. Jackson was absolutely insisting on finding an action property and I could not get it to consider anything else. So, I had to selectively disable UNWRAP_ROOT_PROPERTY for certain domain objects, so that Jackson would be open to parsing alternatives. I modified the project's ContextResolver.getContext(...) implementation to check for a #JsonRootName annotation - since this only has meaning if wrapping is enabled, I used the presence of this annotation to determine whether to return an object mapper configured with root property wrapping on, or off.
At this stage, I might have been able to use #JsonTypeInfo(include=JsonTypeInfo.As.WRAPPER_OBJECT, ...), except for the issue with the null payload mentioned above (this is used to indicate that the child object has no properties - if the spec I was working from had given an empty object {} instead then there would not be a problem). So to proceed I needed a custom type resolver.
I created a new class that extended org.codehaus.jackson.map.TypeDeserializer, with the purpose that whenever Jackson is called to deserialize a BaseAction instance, it will call this custom deserializer. The deserializer will be given a subtypes array, which for BaseAction maps first-action, second-action, etc. to FirstAction.class, etc. The deserializer reads the input stream for the field name, then matches the name to a class. If the next token is an object, then it finds and delegates to the appropriate deserializer for that class, or if it is null it finds the no-args constructor and invokes it to get an object.
A class that implements org.codehaus.jackson.map.jsontype.TypeResolverBuilder is needed that can build an instance of this previous class, and then the TypeResolverBuilder is given as a #JsonTypeResolver annotation on the BaseAction class.

Ignore overloaded methods in a composed object

Consider the following example:
#JsonIgnoreProperties(ignoreUnknown = true)
class ModelA {
private ModelB modelB;
}
ModelB introduces two overloaded methods such as setProperty(String) and setProperty(Object). This confuses Jackson's ObjectMapper complaining about "confliciting setter definitions":
Caused by: java.lang.IllegalArgumentException:
Conflicting setter definitions for property "property": ModelB#setProperty(1 params) vs ModelB#setProperty(1 params)
I'm aware that if I can use inheritance then I can use #JsonIgnore as in proposed in this answer. I'm also aware that a bottom-line solution would be to develop a custom Jackson deserializer (although very complex in my case). But, I'd like to know if there is a workaround for this if I'm restricted to use composition?

Categories

Resources