0

I have implemented a wrapper over OkHttpClient. Which has two classes Get and Post that can be executed to get response of type specified with the instance of Get and Post. It looks like below,

HttpMethod is the base class looks like

@Data
@NoArgsConstructor
@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        property = "type",
        visible = true
)
@JsonSubTypes({@JsonSubTypes.Type(
        value = Get.class,
        name = HttpMethodType.GET_NAME
),@JsonSubTypes.Type(
        value = Post.class,
        name = HttpMethodType.POST_NAME
)})
public abstract class HttpMethod<R> {

    protected static final int DEFAULT_TIMEOUT_IN_MILLIS = 60000; // 60 seconds

    private HttpMethodType type;

    protected HttpConfiguration httpConfiguration;

    // Start path with a `/`, also include query string
    protected String path;

    protected Map<String, String> headers;

    private TypeReference<R> outputType;

    protected HttpMethod(HttpMethodType type,
                         @Valid HttpConfiguration httpConfiguration,
                         Map<String, String> headers,
                         TypeReference<R> outputType,
                         String path) {

        this.type = type;
        this.httpConfiguration = httpConfiguration;
        this.headers = headers;
        this.outputType = outputType;
        this.path = path;
    }

    /**
     * Object will be parsed object of type R if type is not null, else it will be string
     */
    public R execute() throws IOException {
        Response response = executeCall();
        return parse(response);
    }

    protected abstract Response executeCall() throws IOException;

    protected GenericErrorResponse parseUnsuccessfulResponse(Response response) {
        if (!response.isSuccessful()) {
            try (ResponseBody responseBody = response.body()) {
                String json = responseBody.string();
                return MapperUtils.deserialize(json, GenericErrorResponse.class);
            } catch (IOException e) {
                throw TarkshalaServiceException.create(SerializationErrorCode.getInstance());
            }
        }

        throw TarkshalaServiceException.create(SerializationErrorCode.getInstance());
    }

    protected R parse(Response response) {

        if (!response.isSuccessful()) {
            GenericErrorResponse errorResponse = parseUnsuccessfulResponse(response);
            throw TarkshalaServiceException.create(NonSuccessResponseErrorCode.getInstance(response.code(), errorResponse.getErrorCode()));
        }

        try (ResponseBody responseBody = response.body()) {
            String json = responseBody.string();
            if (outputType != null) {
                return MapperUtils.deserialize(json, outputType);
            }

            throw TarkshalaServiceException.create(SerializationErrorCode.getInstance());
        } catch (IOException e) {
            throw TarkshalaServiceException.create(SerializationErrorCode.getInstance());
        }
    }

    public String getUrl() {
        return String.format("%s%s", httpConfiguration.url(), path);
    }
}

Get and Post implement it as following

@Data
@NoArgsConstructor
public class Get<R> extends HttpMethod<R> {

    @Builder
    public Get(@Valid HttpConfiguration httpConfiguration,
               Map<String, String> headers,
               TypeReference<R> outputType,
               String path) {
        super(HttpMethodType.GET, httpConfiguration, headers, outputType, path);
    }

    protected Response executeCall() throws IOException {
        OkHttpClient client = new OkHttpClient()
                .newBuilder()
                .callTimeout(DEFAULT_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS)
                .build();

        Request.Builder requestBuilder = new Request.Builder();
        requestBuilder.url(getUrl());

        for (Map.Entry<String, String> entry : headers.entrySet()) {
            requestBuilder.addHeader(entry.getKey(), entry.getValue());
        }

        Request request = requestBuilder.get().build();

        return client.newCall(request).execute();
    }
}
@Data
@NoArgsConstructor
public class Post<R> extends HttpMethod<R> {

    private Object requestBody;

    @Builder
    public Post(HttpConfiguration httpConfiguration,
                   Map<String, String> headers,
                   TypeReference<R> outputType,
                   String path,
                   Object requestBody) {
        super(HttpMethodType.POST, httpConfiguration, headers, outputType, path);
        this.requestBody = requestBody;
    }

    @Override
    protected Response executeCall() throws IOException {
        OkHttpClient client = new OkHttpClient()
                .newBuilder()
                .callTimeout(DEFAULT_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS)
                .build();

        Request.Builder requestBuilder = new Request.Builder();
        requestBuilder.url(getUrl());

        for (Map.Entry<String, String> entry : headers.entrySet()) {
            requestBuilder.addHeader(entry.getKey(), entry.getValue());
        }

        RequestBody body = RequestBody.create(okhttp3.MediaType.parse(MediaType.APPLICATION_JSON), MapperUtils.serializeAsJson(this.requestBody));

        Request request = requestBuilder.post(body).build();

        return client.newCall(request).execute();
    }
}

I want to serialize and deserialize these, in order to store these to db and send over wire. But I am getting following error while deserializing

ERROR [2023-05-28 02:13:21,576] io.dropwizard.jersey.jackson.JsonProcessingExceptionMapper: Error handling a request: 3cddc3a0922e1cbe
! com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.fasterxml.jackson.core.type.TypeReference` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
!  at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 11, column: 19] (through reference chain: com.tarkshala.tavahot.ms.clockwork.models.ScheduleJobRequest["httpRequest"]->com.tarkshala.common.http.Get["outputType"])
! at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
! at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1904)
! at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
! at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1349)
! at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
! at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)

I understand that this is happening because Jackson need Concrete class to deserialize. Is there a way around to achieve this?

Kuldeep Yadav
  • 1,664
  • 5
  • 23
  • 41
  • See the linked duplicate, and in particular [this link to the Jackson docs](https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization) from there. – yshavit May 28 '23 at 04:24

0 Answers0