1

I'm trying to deserialize a JSON result of a search API. The search API (and I have simplified here) has a main object with metadata about the search and then has a list of whatever the searched object is. I'm trying to achieve this using a list of generic objects

I have tested these classes without the and everything works exactly as I would want it to.

TestSearch.java

public class TestSearch<T> {

    private String count;
    private List<T> results;

    public String getCount() {
        return count;
    }

    public void setCount(String count) {
        this.count = count;
    }

    public List<T> getResults() {
        return results;
    }

    public void setResults(List<T> results) {
        this.results = results;
    }
}

TestResult.java

public class TestResult {

    private String name;
    private String description;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

TestController.java

@RequestMapping("/test/json")
public String testGetJson() {
    return "{\"count\":3,\"results\":[{\"name\":\"result 1\",\"description\":\"this is the first result\",\"extra\":\"property\"},{\"name\":\"result 2\",\"description\":\"tqbf\"},{\"name\":\"result 3\",\"description\":\"jotlz\"}]}";
}

@RequestMapping("/test/testserialize")
public List<TestResult> testSerialize() {
    TestSearch<TestResult> testSearch = new RestTemplate().getForObject("http://localhost:8957/test/json", new TestSearch<TestResult>().getClass());

    List<TestResult> results = testSearch.getResults();

    results.forEach(result -> {
        result.setName("new value");
    });

    return results;
}

JSON (just to make things more readable)

{
    "count": 3,
    "results": [
        {
            "name": "result 1",
            "description": "this is the first result",
            "extra": "property"
        },
        {
            "name": "result 2",
            "description": "tqbf"
        },
        {
            "name": "result 3",
            "description": "jotlz"
        }
    ]
}

After calling the endpoint /test/testserialize my application throws this error

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.testapp.entity.TestResult

Now, if I go back and update the TestSearch class to:

public class TestSearch {

    private String count;
    private List<TestResult> results;

    public String getCount() {
        return count;
    }

    public void setCount(String count) {
        this.count = count;
    }

    public List<TestResult> getResults() {
        return results;
    }

    public void setResults(List<TestResult> results) {
        this.results = results;
    }
}

I get the results I'm expecting:

[
    {
        "name": "new value",
        "description": "this is the first result"
    },
    {
        "name": "new value",
        "description": "tqbf"
    },
    {
        "name": "new value",
        "description": "jotlz"
    }
]
AKrush95
  • 1,381
  • 6
  • 18
  • 35
  • You have tried this tutorial ?? http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html – Rohan Kadu Dec 19 '17 at 17:03
  • @Rohan i haven't seen this one yet, but I do notice that it's for JaxB and XML when I'm looking for Jackson and JSON. It might be a good starting point to look into an equivalent to @ XmlAnyElement thanks – AKrush95 Dec 19 '17 at 17:19

2 Answers2

2

The getClass() method inside of the getForObject function does not see the type

To solve this, use a ParameterizedTypeReference

    TestSearch<TestResult> testSearch = new RestTemplate().exchange(
            "http://localhost:8957/test/json", 
            HttpMethod.GET, 
            null, 
            new ParameterizedTypeReference<TestSearch<TestResult>>() {}).getBody();

I finally tracked down a solution that happens to be 6 years old: Generics with Spring RESTTemplate

AKrush95
  • 1,381
  • 6
  • 18
  • 35
0

You need to create a TypeReference object for each generic type you use and use that for deserialization. For example,

           ObjectMapper mapper = new ObjectMapper();

            Map<String, Object> map = new HashMap<String, Object>();

            // convert JSON string to Map
            map = mapper.readValue(json, new TypeReference<Map<String, String>>(){});

In your case you will have to create the type reference for

   public List<T> getResults()

Please let me know if you need additional help in it.

Rohan Kadu
  • 1,311
  • 2
  • 12
  • 22
  • Thanks, I actually just figured it out using a ParameterizedTypeReference inside of the RestTemplate exchange method – AKrush95 Dec 19 '17 at 17:58