mercredi 28 décembre 2022

How to deserialize to map or array class where map structure varies in Java?

I need to deserialize the response of an external REST API which is usually a JSON object but if an error occurred it will be an array of JSON objects. The API is lacking in documentation so the way I determine if error occurred or not is by the HTTP Status of the response. The problem is that non-error responses have different structure per API route (user response, product response etc.). Another problem is in my application we use an abstract class for external API response with such fields as error, hasError. I solved the problem as follows:

 public abstract class AbsractApiResponse {
  public abstract String getError();
  public abstract void setError(String error);
  public abstract String getDescription();
  public abstract void setDescription(String error);
}

public class ErrorResponse {
  private String errorCode; // getters/setters

  private String message; // getters/setters
}

public class UserResponse extends AbsractApiResponse {

  private String error; // getters/setters
  private boolean hasError; // getters/setters
  private boolean description; // getters/setters
  private String userName;
  private String userEmail;
}


public <R extends AbsractApiResponse> R deserializeResponse(
    String apiResponse, Class<R> responseType, boolean isHttpError)
    throws JsonProcessingException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
  R response;
  Object deserialized = objectMapper.readValue(apiResponse, Object.class);

  if (isHttpError && deserialized instanceof List) {
    TypeReference<List<ErrorResponse>> errorResponseType = new TypeReference<>() {};
    List<ErrorResponse> responseErrors = objectMapper.convertValue(deserialized,
        errorResponseType);
    Constructor<R> constructor = responseType.getDeclaredConstructor();
    response = constructor.newInstance();
    ErrorResponse firstError = responseErrors.get(0);
    String errorDescription = responseErrors.stream().map(ErrorResponse::toString).collect(Collectors.joining());
    response.setError(firstError.getMessage());
    response.setDescription(errorDescription);
  } else {
    response = objectMapper.convertValue(deserialized, responseType);
  }
  return response;
}

With this approach I would have to add fields like error/hasError etc. to every class which represents a response which isn't that bad I guess. Another red flag for me is the use of reflection (responseType.getDeclaredConstructor()) and the 4 checked exceptions that go with it. I'm wondering, if there's a better way to solve this?

Aucun commentaire:

Enregistrer un commentaire