skip to Main Content

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?

2

Answers


  1. The string you get is with escaping of symbol ". So instead of " you see " which is what you’d have to type in the code if you want to have symbol " in your String. So in your code run the code where you will replace every occurrence of " with " and than run your code on the new String and that should work. So, change your code to this:

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

    This should solve your problem.
    Also, I wrote a small utility that can help you serializing and deserializing Json that is a thin wrapper over ObjectMapper class. It allows you to just call some static method without instanciating an ObjectMapper class. In this case you can replace in your code the line

      response = objectMapper.readValue(responseBody, ApiResponse);
    

    with the line:

    response = JsonUtils.readObjectFromJsonString(responseBody, ApiResponse.class);
    

    If you are interested here is Javadoc for JsonUtils class. It comes as part of Open Source MgntUtils library written and maintained by me. You can get the library as a maven artifact from Maven Central or as a Jar (also with source code and Javadoc available) from Github

    Login or Signup to reply.
  2. 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);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search