122

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)?

9
  • Cannot be cast to what? What is the rest of the error message? Commented Mar 3, 2015 at 0:02
  • 1
    @OliverCharlesworth also added entire stacktrace Commented Mar 3, 2015 at 0:05
  • 2
    What's an Account? Why are you trying to cast to it from a map? Commented Mar 3, 2015 at 0:06
  • For those of us who might be unfamiliar with the library, can you say what class this given() method is statically imported from? Commented Mar 3, 2015 at 0:07
  • @DaveNewton Account is a model from Dropwizard which uses com.fasterxml.jackson.databind.annotations Commented Mar 3, 2015 at 0:08

10 Answers 10

157

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);
3
  • You can also set the response asString(), saves having to do extra conversions - readValue will accept a String as the first arg.
    – BIGDeutsch
    Commented Jun 9, 2016 at 9:25
  • @BIGDeutsch: Just revisiting this, there was no need for me to convert back to tokens there when convertValue could do it in one step. Going to a string works too. Commented Mar 6, 2019 at 17:07
  • what to do when an object contains a varibale which is list . How to deserialize the object using jackson ? Commented Oct 9, 2021 at 12:29
71

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.

1
  • 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. Commented Jun 20, 2017 at 21:02
45

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;
}
1
  • 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<List<T>>(){}.where(new TypeParameter<T>(){}, tClass). But Jackson doesn't take TypeTokens so you then need .getType(). Commented Nov 24, 2017 at 15:22
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));
1
  • How did you set up your constructor for the Document class? Commented Jan 27, 2022 at 11:11
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;
}
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 >>(){ });
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>>() {});
      }
    }
 }
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>>() {});
0

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

  1. 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);
        }
    }
  1. 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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.