How to deserialize Spring's ResponseEntity with Jackson ObjectMapper (probably with using @JsonCreator and Jackson mixins)
Asked Answered
D

2

5

The class ResponseEntity doesn't have a default constructor. Then in order to deserialize it with objectMapper I decided to use the approach given by araqnid in that answer. Shortly - it needs to use mixin feature of Jackson coupled with @JsonCreator.

In my case (with ResponseEntity) it doesn't work out yet due to different reasons.

My test method looks like this:

public static void main(String[] args) throws Exception {
    ResponseEntity<Object> build = ResponseEntity.ok().build();

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.addMixIn(ResponseEntity.class, ResponseEntityMixin.class);

    String s = objectMapper.writeValueAsString(build);
    ResponseEntity<Object> result = objectMapper.readValue(s, ResponseEntity.class);

    System.out.println(result);
}

Firstly, I tried to use the shortest constructor for mixin:

public abstract static class ResponseEntityMixin {
    @JsonCreator
    public ResponseEntityMixin(@JsonProperty("status") HttpStatus status) {
    }
}

In this case I got an assertion error since ResponseEntity has this line of code inside it's constructor:

Assert.notNull(status, "HttpStatus must not be null");

Then I switched the mode of @JsonCreator to DELEGATING but in this case I got another exception:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `org.springframework.http.HttpStatus` from String "headers": value not one of declared Enum instance names: [UPGRADE_REQUIRED, UNAVAILABLE_FOR_LEGAL_REASONS, SERVICE_UNAVAILABLE, CHECKPOINT, LOCKED, METHOD_FAILURE, FAILED_DEPENDENCY, UNPROCESSABLE_ENTITY, PROCESSING, PROXY_AUTHENTICATION_REQUIRED, METHOD_NOT_ALLOWED, GONE, MULTIPLE_CHOICES, GATEWAY_TIMEOUT, ACCEPTED, TEMPORARY_REDIRECT, INTERNAL_SERVER_ERROR, URI_TOO_LONG, LOOP_DETECTED, PAYLOAD_TOO_LARGE, EXPECTATION_FAILED, MOVED_TEMPORARILY, REQUEST_ENTITY_TOO_LARGE, NOT_EXTENDED, CREATED, RESET_CONTENT, BAD_GATEWAY, CONFLICT, VARIANT_ALSO_NEGOTIATES, NETWORK_AUTHENTICATION_REQUIRED, NOT_FOUND, LENGTH_REQUIRED, INSUFFICIENT_SPACE_ON_RESOURCE, NO_CONTENT, OK, FOUND, SEE_OTHER, BANDWIDTH_LIMIT_EXCEEDED, REQUEST_HEADER_FIELDS_TOO_LARGE, PERMANENT_REDIRECT, NOT_ACCEPTABLE, MOVED_PERMANENTLY, REQUEST_TIMEOUT, UNAUTHORIZED, USE_PROXY, IM_USED, ALREADY_REPORTED, PARTIAL_CONTENT, PRECONDITION_FAILED, REQUEST_URI_TOO_LONG, BAD_REQUEST, INSUFFICIENT_STORAGE, CONTINUE, NON_AUTHORITATIVE_INFORMATION, REQUESTED_RANGE_NOT_SATISFIABLE, UNSUPPORTED_MEDIA_TYPE, I_AM_A_TEAPOT, HTTP_VERSION_NOT_SUPPORTED, SWITCHING_PROTOCOLS, NOT_MODIFIED, NOT_IMPLEMENTED, TOO_MANY_REQUESTS, DESTINATION_LOCKED, PAYMENT_REQUIRED, FORBIDDEN, PRECONDITION_REQUIRED, MULTI_STATUS]
 at [Source: (String)"{"headers":{},"body":null,"statusCode":"OK","statusCodeValue":200}"; line: 1, column: 2]

Also I tried to use the all-arguments constructor of ResponseEntity but it was also unsuccessful because of problems with MultiValueMap deserialization (but I believe that if I fixed it then it would finally bring me to the same problem as described above).

Could anyone help me solving this problem? Maybe it's impossible to use mixins in this case at all?

If you know the another approaches how to deserialize ResponseEntity with Jackson - please give them too.

Here is the source code of my tests: https://github.com/amseager/responseentitydeserialization

Deanadeanda answered 21/11, 2019 at 19:17 Comment(1)
This is an extremely well defined question!Naca
G
5

Using MixIns is a good way to solve it:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

public class Main {
    public static void main(String[] args) throws Exception {
        ResponseEntity<Object> entity = ResponseEntity
                .ok()
                .header("header", "value")
                .body("Everything is OK!");

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.addMixIn(ResponseEntity.class, ResponseEntityMixin.class);
        objectMapper.addMixIn(HttpStatus.class, HttpStatusMixIn.class);

        String json = objectMapper.writeValueAsString(entity);
        TypeReference ref = new TypeReference<ResponseEntity<Object>>() {
        };
        ResponseEntity<Object> result = objectMapper.readValue(json, ref);
        System.out.println(result);
        System.out.println(result.equals(entity));
    }
}

@JsonIgnoreProperties(ignoreUnknown = true)
class ResponseEntityMixin {
    @JsonCreator
    public ResponseEntityMixin(@JsonProperty("body") Object body,
                               @JsonDeserialize(as = LinkedMultiValueMap.class) @JsonProperty("headers") MultiValueMap<String, String> headers,
                               @JsonProperty("statusCodeValue") HttpStatus status) {
    }
}

class HttpStatusMixIn {

    @JsonCreator
    public static HttpStatus resolve(int statusCode) {
        return HttpStatus.NO_CONTENT;
    }
}

Above code prints:

<200 OK OK,Everything is OK!,[header:"value"]>

and
true which means source and deserialised objects are the same.

Gennie answered 21/11, 2019 at 21:44 Comment(3)
Big thanks! So it was a bit more complicated task. Also I made some mistakes, need to dig deeper into the subject.Deanadeanda
I have a similar scenario where I need to convert a json response string to ResponseEntity<String> I tried the above solution but I am getting the following error: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of org.springframework.http.ResponseEntity (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)Trickle
@Poojasingh, I gues, you do not have to deserialise it to ResponseEntity<String>. You probably can just create a new object using some factory method: ResponseEntity.of(json).Naca
P
4

You need to add ResponseEntityDecoder to able to parse ResponseEntity :

Feign.Builder builder = Feign.builder()
            .encoder(new JacksonEncoder(objectMapper))
            .decoder(new ResponseEntityDecoder(new JacksonDecoder(objectMapper)))

Then it should work

Propagandize answered 7/4, 2022 at 9:41 Comment(2)
thanks but I think the question wasn't related to FeignDeanadeanda
I had a similar problem and this definitely solved my issue; I was expecting an exception saying that it didn't find a valid instance of ResponseEntity decoder; applying the ResponseEntityDecoder definitely fiexd my problem. Thanks!Keese

© 2022 - 2024 — McMap. All rights reserved.