120

I'm getting below error:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.testing.models.Account

with below code

final int expectedId = 1;

Test newTest = create();

int expectedResponseCode = Response.SC_OK;

ArrayList<Account> account = given().when().expect().statusCode(expectedResponseCode)
    .get("accounts/" + newTest.id() + "/users")
    .as(ArrayList.class);
assertThat(account.get(0).getId()).isEqualTo(expectedId);

Is there a reason why I cannot do get(0)?

Passionate Engineer
  • 10,034
  • 26
  • 96
  • 168

9 Answers9

150

The issue's coming from Jackson. When it doesn't have enough information on what class to deserialize to, it uses LinkedHashMap.

Since you're not informing Jackson of the element type of your ArrayList, it doesn't know that you want to deserialize into an ArrayList of Accounts. So it falls back to the default.

Instead, you could probably use as(JsonNode.class), and then deal with the ObjectMapper in a richer manner than rest-assured allows. Something like this:

ObjectMapper mapper = new ObjectMapper();

JsonNode accounts = given().when().expect().statusCode(expectedResponseCode)
    .get("accounts/" + newClub.getOwner().getCustId() + "/clubs")
    .as(JsonNode.class);


//Jackson's use of generics here are completely unsafe, but that's another issue
List<Account> accountList = mapper.convertValue(
    accounts, 
    new TypeReference<List<Account>>(){}
);

assertThat(accountList.get(0).getId()).isEqualTo(expectedId);
Mark Peters
  • 80,126
  • 17
  • 159
  • 190
70

Try the following:

POJO pojo = mapper.convertValue(singleObject, POJO.class);

or:

List<POJO> pojos = mapper.convertValue(
    listOfObjects,
    new TypeReference<List<POJO>>() { });

See conversion of LinkedHashMap for more information.

ErikE
  • 48,881
  • 23
  • 151
  • 196
user2578871
  • 820
  • 6
  • 5
  • 3
    +1 for showing "convertValue". I had a case where I needed to read a particular property out of json as a List and this is exactly what I needed. – GameSalutes Jun 20 '17 at 21:02
44

The way I could mitigate the JSON Array to collection of LinkedHashMap objects problem was by using CollectionType rather than a TypeReference . This is what I did and worked:

public <T> List<T> jsonArrayToObjectList(String json, Class<T> tClass) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    CollectionType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, tClass);
    List<T> ts = mapper.readValue(json, listType);
    LOGGER.debug("class name: {}", ts.get(0).getClass().getName());
    return ts;
}

Using the TypeReference, I was still getting an ArrayList of LinkedHashMaps, i.e. does not work:

public <T> List<T> jsonArrayToObjectList(String json, Class<T> tClass) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    List<T> ts = mapper.readValue(json, new TypeReference<List<T>>(){});
    LOGGER.debug("class name: {}", ts.get(0).getClass().getName());
    return ts;
}
Himadri Pant
  • 2,171
  • 21
  • 22
  • 3
    Saved my problem as well. Thank you! – Vladimir Gilevich Oct 05 '17 at 14:53
  • 1
    +1, the difference in your case was that the method itself was generic so `T` could not be reified via the TypeToken, which is typically easier. Personally I prefer Guava's helper because it's still typesafe and not specific to any container type: `return new TypeToken>(){}.where(new TypeParameter(){}, tClass)`. But Jackson doesn't take `TypeToken`s so you then need `.getType()`. – Mark Peters Nov 24 '17 at 15:22
  • Saved my problem as well. Thank you! – Shashank Jul 17 '20 at 03:32
  • Thank you! Spent a lot of time to fix this error untill i found this answer!! – glco Dec 08 '20 at 18:33
  • Lovely!!. This saved my day as well :)..Struggled for hours before i hit this solution. I was trying to write a genric JSONB array convertor for JOOQ in kotlin. – sumanr Feb 23 '21 at 12:45
6

I had a similar exception (but different problem) - java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to org.bson.Document , and fortunately it's solved easier:

Instead of

List<Document> docs = obj.get("documents");
Document doc = docs.get(0)

which gives error on second line, One can use

List<Document> docs = obj.get("documents");
Document doc = new Document(docs.get(0));
nurb
  • 325
  • 3
  • 6
1

Solve problem with two method parse common

  1. Whith type is an object
public <T> T jsonToObject(String json, Class<T> type) {
        T target = null;
        try {
            target = objectMapper.readValue(json, type);
        } catch (Jsenter code hereonProcessingException e) {
            e.printStackTrace();
        }
    
        return target;
    }
  1. With type is collection wrap object
public <T> T jsonToObject(String json, TypeReference<T> type) {
    T target = null;
    try {
        target = objectMapper.readValue(json, type);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
    return target;
}
Love Java
  • 11
  • 2
1

This is something i used in my project, Json object was returned, i converted it to a List of POJO, List and then accessed the element. I took the input of Json object from another microservice.

Main thing is:- JsonNode stocks = restTemplate.getForObject("http://localhost:2000/stocks/qty", JsonNode.class); List<Stock_id_qty> stockList = mapper.convertValue(stocks, new TypeReference<List<Stock_id_qty>>() {});

@GetMapping("/")
    public List<Stock_id_qty> checkQty() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode stocks = restTemplate.getForObject("http://localhost:2000/stocks/qty", JsonNode.class);
        List<Stock_id_qty> stockList = mapper.convertValue(stocks, new TypeReference<List<Stock_id_qty>>() {});
        List<Stock_id_qty> result = new ArrayList<>();
        for(Stock_id_qty s : stockList){
            if(s.getStockQty() < 10)
            {
                result.add(s);
            }
        }
        return result;
    }
0

I have this method for deserializing an XML and converting the type:

public <T> Object deserialize(String xml, Class objClass ,TypeReference<T> typeReference ) throws IOException {
    XmlMapper xmlMapper = new XmlMapper();
    Object obj = xmlMapper.readValue(xml,objClass);
    return  xmlMapper.convertValue(obj,typeReference );   
}

and this is the call:

List<POJO> pojos = (List<POJO>) MyUtilClass.deserialize(xml, ArrayList.class,new TypeReference< List< POJO >>(){ });
Ishaan Javali
  • 1,711
  • 3
  • 13
  • 23
0

When you use jackson to map from string to your concrete class, especially if you work with generic type. then this issue may happen because of different class loader. i met it one time with below scenarior:

Project B depend on Library A

in Library A:

public class DocSearchResponse<T> {
 private T data;
}

it has service to query data from external source, and use jackson to convert to concrete class

public class ServiceA<T>{
  @Autowired
  private ObjectMapper mapper;
  @Autowired
  private ClientDocSearch searchClient;

  public DocSearchResponse<T> query(Criteria criteria){
      String resultInString = searchClient.search(criteria);
      return convertJson(resultInString)
  }
}

public DocSearchResponse<T> convertJson(String result){
     return mapper.readValue(result, new TypeReference<DocSearchResponse<T>>() {});
  }
}

in Project B:

public class Account{
 private String name;
 //come with other attributes
}

and i use ServiceA from library to make query and as well convert data

public class ServiceAImpl extends ServiceA<Account> {
    
}

and make use of that

public class MakingAccountService {
    @Autowired
    private ServiceA service;
    public void execute(Criteria criteria){
      
        DocSearchResponse<Account> result = service.query(criteria);
        Account acc = result.getData(); //  java.util.LinkedHashMap cannot be cast to com.testing.models.Account
    }
}

it happen because from classloader of LibraryA, jackson can not load Account class, then just override method convertJson in Project B to let jackson do its job

public class ServiceAImpl extends ServiceA<Account> {
        @Override
        public DocSearchResponse<T> convertJson(String result){
         return mapper.readValue(result, new TypeReference<DocSearchResponse<T>>() {});
      }
    }
 }
uncle bob
  • 570
  • 1
  • 10
  • 21
0
public class ObjectHelper {

  private static ObjectMapper objectMapper = new ObjectMapper();

  public static ObjectMapper getObjectMapper() {
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
    return objectMapper;
  } 
}

Use

FetchResponse fetchResponse =
ObjectHelper.getObjectMapper().convertValue(
                    data, new TypeReference<FetchResponse>() {});

OR

List<Map<String, Object>> responseObj = (List<Map<String, Object>>) response.get("content");

List<LkAuthUserDetail> responseData = ObjectHelper.getObjectMapper().convertValue(responseObj,
                    new TypeReference<List<LkAuthUserDetail>>() {});
CodingBee
  • 1,011
  • 11
  • 8