Jackson deserializer priority? - spring-boot

I have a Spring Boot app that is modeling ActityStreams objects and for the most part Jackson's Polymorphic Deserialization works well.
There are 'objects' in the JSON which are references (links) and not JSON objects with type information. For instance
"actor":"https://some.actors.href/ rather than
"actor":{
"type":"Actor",
"name":"SomeActor"
}
I've written custom deserializers and and placed them on the fields to deal with this
#JsonDeserialize (using = ActorOrLinkDeserializer.class)
private Actor actor;
However my ActorOrLinkDeserializer is instantiated but never called and Jackson complains with Missing type id when trying to resolve subtype of [simple type, class org.w3.activity.streams.Actor]: missing type id property 'type' (for POJO property 'actor') which is from the polymorphic deserializer.
It appears that the polymorphic deserialization code takes precedence over my local #JsonDeserialize annotation and I need a way to force my code to run first.
I've tried using my own ObjectMapper rather than Boot's and there's no difference.
I'd appreciate pointers and suggestions.

It turns-out there's a fairly simple solution to this problem using a DeserializationProblemHandler.
What I've implemented that works for all test cases so far is
1.
objectMapper.addHandler(new DeserProblemHandler());
or register with Spring Boot.
2.
public class DeserProblemHandler extends DeserializationProblemHandler {
public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) {
return TypeFactory.defaultInstance().constructType(baseType.getRawClass());
}
}
Add a constructor to each of the polymorphic classes that takes a string argument which is the href.

Related

Deserializing a Enum with Jackson in Kotlin

I'm trying to serialize and deserialize an enum with Jackson.
My enum:
public enum class Type {
#JsonProperty("Typ A")
TypeA,
#JsonProperty("Typ B")
TypeB,
}
Serializing Type.TypeA results in the desired outcome of "Typ A". However Deserializing "Typ A" results in the following error:
java.lang.IllegalArgumentException: No enum constant de.advisori.pzp.task.TaskType.Typ A
I have tried other variations that I found online, such as this:
public enum class Type (#JsonValue val value: String) {
TypeA("Typ A"),
TypeB("Typ B"),
}
but they all yield the same result. Serialization works, deserialization results in the error above.
How do I correctly deserialize an enum with Jackson?
If it makes any difference: I am using it in a Spring Boot RequestMapping as a #RequestParam and return value.
As #dnault pointed out, Jackson isn't used for deserialization here. #RequestParams are never treated as JSON, hence Jackson is never used on them.
Two possible solutions are:
Using Kotlins ability to use spaces in names:
public enum class Type { `Typ A`, `Typ B` } (suggested by #DodgyCodeException)
Using a explicitly defined converter: https://stackoverflow.com/a/69031139/12898394 (pointed in the right direction by #Michal Ziober
I don't think any annotations will work to change the enum values. For this you need to write your own Serializer and Deserializer.
You will likely want to do this:
Create a Serializer by subclassing StdSerializer
Create a Deserializer by subclassing StdDeserializer
If you intend on using the enum as a key in JSON you will need KeyDeserializer too
Create a Module to wrap these up that you can pass to the configuration of Jackson, for that you use SimpleModule
There are many tutorials for this, e.g. https://www.baeldung.com/jackson-deserialization

What does Spring use to do deserialization?

I have the following class:
data class Thing(val lines: List<String>)
The JSON representation is:
{
"lines": [
"something",
"something else"
]
}
Spring WebFlux can successfully parse this with the following:
// Parse the JSON as an object and return it.
request -> ServerResponse.ok().body(request.bodyToMono(Thing::class.java)
However, using Jackson directly with either of the following techniques fails:
val mapper = ObjectMapper()
val item = mapper.readValue<Thing>("""{"lines":["something","something else"]}""")
ServerResponse.ok().body(request.bodyToMono(Map::class.java)
.map { map ->
val mapper = ObjectMapper()
val tmp = mapper.convertValue(map, Thing::class.java)
}
The error is:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Thing` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
I thought that Spring was using Jackson to do its transformation. And I thought that Jackson could cope with such basic transformations from JSON -> POJOs. Using a #JsonDeserialize class obviously works. So why does the first example work and the second not?
Spring uses Jackson but it registers custom handlers and modules when it creates its default instance of ObjectMapper.
Jackson has special handling for constructors that take a single argument. This was done to support classes like UUID and URI. To instruct Jackson to not use this technique, annotate your constructor with #JsonCreator.
data class Thing #JsonCreator constructor(val lines: List<String>)
I have not reviewed Spring's reactive code so I do not know what or if it does something to disable Jackson's special handling.

Passing of list of type Baseclass between Webservices involving generics and conversion of polymorphic types

I have two REST services using Spring Boot running on two different servers. I am using REST Template for this communication.
There are some models that are shared by these two services. All these models are of type 'IDataToTransferred' .
'IDataToTransferred' is a marker Interface implemented by various Model Beans.
I need to write a common logic for passing a list of these models between these REST services.
Hence I wrote a logic which uses parameters
List<? extends IDataToTransferred> from Sender service to Receiver Service.
Update: With Some Code
IDataToTransferred.java is a marker Interface
DataToBeSent.java
DataToBeSent Implements IDataToTransferred{
//Simple Pojo
}
SenderService.java
sendData(List<? extends IDataToTransferred> uploadDataObjectList){
//Some Code with REST Template
//restTemplate.postForEntity
}
IDataToTransferred Interface is shared between communicating webservices.
DataToBeReceived.java
DataToBeReceived Implements IDataToTransferred{
//Simple Pojo
}
ReceiverService.java
receiveData(List<? extends IDataToTransferred> uploadDataObjectList){
//Some Code to convert uploadDataObjectList to DataToBeReceived
}
Note In REST service I was always getting 415 error. Unsupported Media type. when I use the same List<? extends IDataToTransferred> on Receiver.
When I changed this to List<? super IDataToTransferred> on Receiver side, now it works, I am guessing because of Producer extends Consumer super rules.
But the problem is that now I can't typecast to the IDataToTransferred type on Receiver Side. Inside the list I am getting all linkedHashmap, the json got converted to linked HashMap between these services.
How can I get DataToBeReceived class object in ReceiverService?
For simplicity sake I have removed Controllers. Assume that they have the same signature as the services.
If I had known better terms to search, I would have found answer before Posting. But alas.
In any case I found the answer in stackoverflow page here together with a this blog ofcourse.
The examples are with abstract classes. I have used with interfaces.
As mentioned in the link. I Introduced below annotation in the marker interface IDataToTransferred:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = DataToBeSent.class, name = "datatransfer")})
The property type is introduced in the bean DataToBeSent as a property. This type param is used as information for conversion into implementing type from interface type. One can use a different variable than one named "type". In JsonSubTypes annotation , we mention the classes that are implementing this interface.
DataToBeSent Implements IDataToTransferred{
//Simple Pojo
// Some Properties with getter and setter
String type = "datatransfer";
//with getter and setter
}
The same exercise needs to be implemented on the Receiver Side also. Hence, we will have annotation as below:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = DataToBeReceived.class, name = "datatransfer")})
Here, we have DataToBeReceived class as implementing the IDataToTransferred interface. Ofcourse you need to add type as property to DataToBeReceived class also as below:
DataToBeReceived Implements IDataToTransferred{
//Simple Pojo
// Some Properties with getter and setter
String type = "datatransfer";
//with getter and setter
}
Hope this helps.

Spring return dynamic instance based of String value

Java Spring question:
I have a interface MyInterface with one method
void exec (String str);
I have many implementation of MyInterface, say Oneimpl, anotherimpl yetanotherimpl...and so on and can keep adding new implementations.
how do I obtain an instance of a specific implementation using just the name of the implementing class passed as a STRING value , say "someRandomImpl"
The code should be dynamic and can provide a instance of new implementations without code change.
implements ApplicationContextAware
it will autowired ApplicationContext object
use the object like
context.getBean(beanName)
then you get the bean

Resttemplate unable to parse array of type T

I'm facing a problem with Spring and restTemplate. I want to send an object (ListResponse) that contains a generic array. The defenition is as follow:
public class ListResponse<T> implements Serializable {
private long total;
private int page;
private int pageSize;
private T[] objects;
I send a request whith restTemplate.getForObject(). As a result I get an object of type ListResponse but the objects array contains an array of LinkedHashMaps instead of an array with objects of type T.
It seems like restTemplate can not convert the elements in the array to their correct type.
How can I make sure that I get an array of objects of type T back ?
I had this problem today and here is the solution that I came up with (actually, that one of my co-workers suggested). We use it with an interface that returns List<MyDto>.
When you call the RestTemplate, don't pass in the generic type.
Define: public class MyDtoListTemplate extends ListTemplate<MyDto>
Then, call
MyDtoListTemplate template = restTemplate.getForObject("url", MyDtoListTemplate .class, mapOfPathVariables);
It's a bummer that you have to define a concrete class that extends/implements the generic type, but then the generic information is available to the jackson deserializer.
I remember I was able to deserialize generic classes with Jackson 2. I had to add MappingJackson2HttpMessageConverter converter to RestTemplate before making any Http calls with it.
RestTemplate template = new RestTemplate();
template.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
Unfortunately there's no easy way of doing it that I know of. The problem is that the RestTemplate is told which object type to expect. As long as all the fields in this object has a corresponding element in the json/xml, everything works fine. In the case of generics, the serializer doesn't know which class to expect so it just turns the map it gets to a java Map.
You will have the same problem if you tried to getForObject for a generic return type.

Resources