I'm getting below error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.testing.models.Accountwith 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)?
10 Answers
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); 6 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.
1The 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;
} 5 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)); 1 Solve problem with two method parse common
- 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; }- 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;
} 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("", 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("", 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; } 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 >>(){ }); 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>>() {}); } } } 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>>() {}); We faced a similar sort of issue in our microservices communication. We had two services approval and transfer, so once a transfer is approved there will be a communication between the above services using Kafka, where the transfer will be the consumer.
Both the services are maintaining a common bean Payload, which has some fields as Object type
@Getter
@Setter
public class Payload extends KafkaParameters implements Serializable { private static final long serialVersionUID = 1L; private Object requestedBy; private Object requestDetails; private Object payload; private Object existingPayload; private Object payloadDifference;
}Initially from the transfer service we'll set the value to the payload attribute inside Payload bean which will be a reference of type TransactionRequest. So, once the consumer in the transfer service is getting the callback from the approval service we are getting the Payload and when we tried to downcast we got this error
@Service
@Slf4j
public class AdjustmentApprovalConsumer { @KafkaListener(topics = TransferConstants.ADJUSTMENT_APPROVAL_TOPIC, containerFactory = "ApprovalConcurrentKafkaListenerContainerFactory") public void listenCdr(@org.springframework.messaging.handler.annotation.Payload Payload approvalRequest) { TransactionRequest request = (TransactionRequest) approvalRequest.getPayload(); }
}We fixed this issues by using two methods
- Convert the payload to a JSON string
public static String asJsonString(final Object obj) { try { return new ObjectMapper().writeValueAsString(obj); } catch (Exception e) { throw new RuntimeException(e); } }- Convert the JSON string to a given bean
public static <T> Object jsonToObject(final String json, Class<T> type) { try { ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(json, type); } catch (Exception e) { throw new RuntimeException(e); } }So the final code was like this
TransactionRequest request = (TransactionRequest) JsonUtils.jsonToObject(JsonUtils.asJsonString(approvalRequest.getPayload()), TransactionRequest.class);This is may not be a conventional way of doing it but surely fixed the issue.