Search code examples
javajacksonspring-datajax-rs

How to use a deserializer Jackson with javax Response readEntity


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 !


Solution

  • So, i figure it out following another response from a topic : Spring RestTemplate with paginated API

    I removed my Page serializer and deserizalizer, and created a RestResponsePage object

    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.data.domain.PageImpl;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Pageable;
    
    import com.fasterxml.jackson.annotation.JsonCreator;
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.databind.JsonNode;
    
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class RestResponsePage<T> extends PageImpl<T> {
    
        @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
        public RestResponsePage(@JsonProperty("content") final List<T> content,
                                @JsonProperty("number") final int number,
                                @JsonProperty("size") final int size,
                                @JsonProperty("totalElements") final Long totalElements,
                                @JsonProperty("pageable") final JsonNode pageable,
                                @JsonProperty("last") final boolean last,
                                @JsonProperty("totalPages") final int totalPages,
                                @JsonProperty("sort") final JsonNode sort,
                                @JsonProperty("first") final boolean first,
                                @JsonProperty("numberOfElements") final int numberOfElements) {
    
            super(content, PageRequest.of(number, size), totalElements);
        }
    
        public RestResponsePage(final List<T> content, final Pageable pageable, final long total) {
            super(content, pageable, total);
        }
    
        public RestResponsePage(final List<T> content) {
            super(content);
        }
    
        public RestResponsePage() {
            super(new ArrayList<>());
        }
    }
    

    And then, i've read my response as a RestResponsePage, and return a Page from my Webclient

    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(new GenericType<RestResponsePage<EtudeDto>>() {});
                }
                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());
        }