Search code examples
javajsonspring-bootjacksonjson-deserialization

Deserializing Json from String with Rest Client and Object Mapper


I'm making a REST API in Java 17 with Spring Boot 3.2.2. My API calls an external REST API. Said API, even though it does return a json, it doesn't do it in the typical way. Instead of a json object, it returns a json string.

This:

"{\"Code\":\"123-456\",\"Number\":1}"

Not this:

{
  "Code": "123-456",
  "Number": 1
}

I have a class to get the response:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse {
  @JsonProperty("Code")
  private String code;
  @JsonProperty("Number")
  private String number;
}

Now with this code:

ApiResponse response = restClient.post()
    .uri(url)
    .contentType(MediaType.APPLICATION_JSON)
    .body(request.toJsonString())
    .retrieve()
    .body(ApiResponse.class);

And this alternative code:

String responseBody = restClient.post()
    .uri(url)
    .contentType(MediaType.APPLICATION_JSON)
    .body(request.toJsonString())
    .retrieve()
    .body(String.class);

ApiResponse response;

try {
  response = objectMapper.readValue(responseBody, ApiResponse);
} catch(...) {
  ...
}

return response;

In both cases, it fails in the deserialization part. In the first case, it's in .body(...) and in the second case, it's in objectMapper.readValue(...).

The exception that is thrown is:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.example.test.models.ApiResponse` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"Code":"123-456","Number":1}')

Again, the json string in question is:

"{\"Code\":\"123-456\",\"Number\":1}"

I can't seem to be able to deserialize it into the class. Am I missing something?


Solution

  • The response in question has gone through something that I call double serialization (self made term). An instance of ApiResponse has been serialized once, resulting in json object - {...}, then this result has been serialized a second time turning it into json string - "...".

    I am not really sure how rest client is interacting with this kind of response (I haven't used it), but the standard 'fix' is for the response to go through two rounds of deserialization - first into java.lang.String, then into whatever class you need.

    String jsonString = """
                "{\\"Code\\":\\"123-456\\",\\"Number\\":1}"
                """;
    ObjectMapper mapper = new ObjectMapper();
    String jsonObject = mapper.readValue(jsonString, String.class);
    ApiResponse response = mapper.readValue(jsonObject, ApiResponse.class);
    System.out.println(response);