0

I am attempting to use OKHttp, Spring, and Jackson to retrieve JSON data from an API. At is most basic, the response from the API looks like:

{
  "_page": 1,
  "_total_items": 1,
  "_items_this_page": 1,
  "_index_start": 1,
  "_index_end": 1,
  "_links": {
    "self": {
      "href": ""
    }
  },
  "some_key": [
    {
      "key": "value1"
    },
    {
      "key": "value2"
    }
  ]
}

I created a generic wrapper class:


public class PageWrapper<T> {

    @JsonProperty("_links")
    private PageLink links;
    
    
    @JsonAlias({"documents", "employees"} )
    private T pgData;



    public T getPgData() {
        return pgData;
    }


    public void setPgData(T pgData) {
        this.pgData = pgData;
    }


    public PageLink getLinks() {
        return links;
    }


    public void setLinks(PageLink links) {
        this.links = links;
    }

    
    
    
}

I created some tests to ensure it was being parsed correctly, and it worked as expected:

    public void testEmployees() throws JsonParseException, JsonMappingException, IOException {
        File file = new File(this.getClass().getClassLoader().getResource("employee.json").getFile());
        var res = this.mapper.readValue(file, new TypeReference<PageWrapper<List<WBEmployee>>>() {
        });
        assertThat(res).isNotNull();
        assertThat(res.getPgData().size()).isGreaterThan(0);
        for(var r : res.getPgData()) {
            assertThat(r.getAddress()).isNotNull();
            assertThat(r.getBirthDate()).isNotBlank();
            assertThat(r.getEmail()).isNotNull();
            assertThat(r.getFirstName()).isNotNull();
            assertThat(r.getLastName()).isNotNull();
        }
    }

The issue does not come until I run another test which interacts with the actual API:

    @Test
    public void testTestList() throws IOException {
        var res = empAPI.getList();
        assertThat(res).isNotNull();
        assertThat(res.size()).isGreaterThan(0);
        for(var r : res) {
            assertThat(r.getAddress()).isNotNull();
            assertThat(r.getBirthDate()).isNotBlank();
            assertThat(r.getEmail()).isNotNull();
            assertThat(r.getFirstName()).isNotNull();
            assertThat(r.getLastName()).isNotNull();
        }
    }

This test calls the following method (through empAPI.getList()):

    @Override
    public <T> PageWrapper<List<T>> doNestedListGet(Class<T> type, HttpUrl url) throws IOException {
        var req = this.reqBuilder.url(url).build();
        try (var res = this.webClient.newCall(req).execute()) {
            if (!res.isSuccessful()) {
                throw new IOException(String.format("API call failed: %s", res));
            }
            var jString = res.body().string();
            return this.mapper.readValue(jString, new TypeReference<PageWrapper<List<T>>>() {
            });
        }
    }

In this example, jString and the contents of the file in the passing tests are the same (looked at them using the Debugger). However, I am receiving a class cast error:

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class myClass

The getList() method called in the failing test looks like this:

    public List<WBEmployee> getList(int page, WBEmploymentStatus employmentStatus) throws IOException {
        var url = this.baseEndpoint.newBuilder()
                .addQueryParameter("page", String.valueOf(page))
                .addQueryParameter("status", employmentStatus.getDescr()).build();

        return this.wbClient.doNestedListGet(WBEmployee.class, url).getPgData();
    }

I am assuming the issue has to with the handling of generics (explicitly passing a class to TypeReference instead of T gets the expected results) , but not quite sure where the issue lies. Any advice would be appreciated.

Added full stack trace:

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.midamcorp.wblib.model.WBEmployee (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.midamcorp.wblib.model.WBEmployee is in unnamed module of loader 'app')
    at com.midamcorp.hcmadapter.client.EmployeeAPITest.testTestList(EmployeeAPITest.java:47)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:84)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:768)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)

KellyM
  • 2,472
  • 6
  • 46
  • 90
  • Could you provide the relevant methods? The exception should come with a stack trace and point you to the exact code line where it happens. Without seeing the problem code I think the JSON is per default parsed into a Map and not into your class. – Sascha Jun 09 '21 at 13:52
  • @Sascha, it has been added. – KellyM Jun 09 '21 at 13:54
  • The full stack trace isn't helpful. The specific line in your testTestList method would be good to specify. – Sascha Jun 09 '21 at 13:58
  • 1
    Check this out : https://stackoverflow.com/a/67866657/4762502 – Gaurav Jeswani Jun 09 '21 at 13:59
  • @Sascha, line 47 (which throws the error) is `for(var r : res)`. However, to clarify, the central question is why does `return this.mapper.readValue(jString, new TypeReference>>() {});` (in `doNestedGetList()` fail to convert the object correctly, whereas `this.mapper.readValue(file, new TypeReference>>() {});` (in the passing test) work just fine> I had assumed, at runtime, passing `PageWrapper>>` to the TypeReference would have been the same as passing `PageWrapper>>`. What am I missing. – KellyM Jun 09 '21 at 14:17
  • 1
    Looks like your problem is similar to this: https://stackoverflow.com/a/6852184/2135838 – Sascha Jun 09 '21 at 14:22
  • @Sascha and @notescrew. Thanks so much for the links and help. I was able to resolve through reading through them and other questions linked/referenced in them. I have used generics (such as LIst) quite a bit in Jackson; I think it was the nesting that gave me difficulties. Had to do: `var t = mapper.getTypeFactory().constructCollectionType(List.class, type); var t2 = mapper.getTypeFactory().constructParametricType(PageWrapper.class, t); return this.mapper.readValue(jString, t2);` – KellyM Jun 09 '21 at 14:41

0 Answers0