javaspring-mvc

Why is ResponseEntity<Foo> geting a different result to ResponseEntity<String>?


For my Java Spring Boot Application (v2.7.12), I am performing a GET request using restTemplate.exchange() which passes in my correct url, HttpEntity with the correct headers, and response type Profile.class.

It assigns this to ResponseEntity<Profile> response

ResponseEntity<Profile> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Profile.class);

Now, when I assign it to a String type: ResponseEntity<String> response instead of Profile, the response.getBody() returns the correct json body, with the correct data: name: random

<200,{"user":{"username='instagram', full_name='instagram'}

However, when I assign it to a Profile type: ResponseEntity<Profile> response, it returns the correct json body, but with invalid data: name: null

<200,{"user":{"username='null', full_name='null'}

What I want to do is assign the exact API attributes to my Profile model class, without needing to parse JSON myself for the HTML type.

    @JsonIgnoreProperties(ignoreUnknown = false)
public class Profile {
    @JsonProperty("username")
    private String username;
    @JsonProperty("full_name")
    private String full_name;
    @JsonProperty("is_private")
    private boolean is_private;
    @JsonProperty("status")
    private String status;
    @JsonProperty("profile_pic_url")
    private String profile_pic_url;
    @JsonProperty("follower_count")
    private int follower_count;
    @JsonProperty("following_count")
    private int following_count;
    @JsonProperty("biography")
    private String biography;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

This is my restTemplate:

@Controller
public class WebController {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        return builder.build();
    }

I know this issue has an easy workaround. I have tried using Jackson API to parse JSON from the String type response body, but I wish this to be Plan B.

I have tried changing the URL formatting, and it makes no difference. The headers are fine. The API itself is not wrong.

Profile profile = restTemplate.getForObject(uri, Profile.class)

I tried using .getForObject which worked before, but I needed to pass in headers, and it can't do that.


Solution

  • Your JSON has a root element named user. You're trying to deserialize assuming that there's no root element. That's why it is not working, Jackson tries to find the fields of the Profile class on the root, but it never finds any of them, because they are wrapped into another object.

    First, configure your ObjectMapper this way (preferably put this code in a @Configuration annotated class:

    @Bean
    public ObjectMapper objectMapper(ObjectMapper objectMapper) {
        objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
        
        return objectMapper;
    }
    

    This will tell object mapper to allow deserialization with root value.

    Now, annotate your Profile class with @JsonRootName:

    @JsonRootName(value = "user")
    public class Profile {
        // ...
    }
    

    This way, on deserialization, Jackson will unwrap your value before deserializing the JSON into a Profile object.