不能将 java.lang. ClassCastException: java.util.LinkedHashMap 强制转换为 com.testing.models.Account

我得到了以下错误:

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

以下代码

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

为什么我不能做 get(0)

259881 次浏览

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

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

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.

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;
}

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>>() {});
}
}
}

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;
}

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;
}
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>>() {});