I am struggling while I am trying to read a response from a webservice (a Page object from Spring Data) with a deserializer.
I would like to be able to retrieve my response, parsing it to a Page object, and defining the type of the content part. This is my deserializer
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.type.CollectionType;
public class PageJsonDeserializer extends StdDeserializer<Page<?>> implements ContextualDeserializer {
public PageJsonDeserializer() {
this(Page.class);
}
protected PageJsonDeserializer(final Class<?> vc) {
super(vc);
}
private static final String CONTENT = "content";
private static final String NUMBER = "number";
private static final String SIZE = "size";
private static final String TOTAL_ELEMENTS = "totalElements";
private JavaType valueType;
@Override
public Page<?> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
final CollectionType valuesListType = ctxt.getTypeFactory().constructCollectionType(List.class, getValueType());
List<?> list = new ArrayList();
int pageNumber = 0;
int pageSize = 0;
long total = 0;
if (p.isExpectedStartObjectToken()) {
p.nextToken();
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
switch (propName) {
case CONTENT:
list = ctxt.readValue(p, valuesListType);
break;
case NUMBER:
pageNumber = ctxt.readValue(p, Integer.class);
break;
case SIZE:
pageSize = ctxt.readValue(p, Integer.class);
break;
case TOTAL_ELEMENTS:
total = ctxt.readValue(p, Long.class);
break;
default:
p.skipChildren();
break;
}
} while (((propName = p.nextFieldName())) != null);
} else {
ctxt.handleUnexpectedToken(handledType(), p);
}
} else {
ctxt.handleUnexpectedToken(handledType(), p);
}
// Note that Sort field of Page is ignored here.
// Feel free to add more switch cases above to deserialize it as well.
return new PageImpl<>(list, PageRequest.of(pageNumber, pageSize), total);
}
/**
* This is the main point here.
* The PageDeserializer is created for each specific deserialization with concrete generic parameter type of Page.
*/
@Override
public JsonDeserializer<?> createContextual(final DeserializationContext ctxt, final BeanProperty property) {
// This is the Page actually
final JavaType wrapperType = ctxt.getContextualType();
final PageJsonDeserializer deserializer = new PageJsonDeserializer();
// This is the parameter of Page
deserializer.valueType = wrapperType.containedType(0);
return deserializer;
}
}
And this is how i try to read my entity
public Page<EtudeDto> findEtudeWithFilterCriteria(final String nomEtude, final Pageable page) {
try {
final var response = etudeWebService.findEtudeWithFilterCriteria(nomEtude, page);
if (Response.Status.Family.SUCCESSFUL == response.getStatusInfo().getFamily()) {
return response.readEntity(Page.class);
}
log.error("Erreur lors de l'appel au webclient findEtudeWithFilterCriteria nom {}", nomEtude);
} catch (final Exception e) {
log.error("Erreur lors de l'appel au webclient findEtudeWithFilterCriteria nom {}", nomEtude, e);
}
return new PageImpl<>(List.of());
}
Right now, my main problem is in createContextual method.deserializer.valueType = wrapperType.containedType(0); return null, and I don't know how to tell him which kind of object is in Content. And without it, it can't build my Page object. The specific thing is I don't want to specify the value Type manually in the deserializer, because sometimes the Page content is not a "EtudeDto" object, but an "EtudeOptique". I would like to do it in a generic way.
This is how my json response looks like on postman
{
"content": [
{
"id": 1,
...
"dateMiseAJour": "2022-07-06T00:00:00"
},
...
],
"pageable": {
"sort": {
"empty": false,
"sorted": true,
"unsorted": false
},
"offset": 0,
"pageSize": 10,
"pageNumber": 0,
"unpaged": false,
"paged": true
},
"last": false,
"totalElements": 21,
"totalPages": 3,
"number": 0,
"size": 10,
"sort": {
"empty": false,
"sorted": true,
"unsorted": false
},
"first": true,
"numberOfElements": 10,
"empty": false
}
Do you have any idea how can I resolve this ? Thanks !