java.lang.ClassCastException: java.util.LinkedHashMap не можна передати на com.testing.models.Account


84

Я отримую нижче помилки:

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


Не можна кинути на що ? Що решта повідомлення про помилку?
Олівер Чарлсворт

1
@OliverCharlesworth також додав цілий стек
Пристрасний інженер

2
Що таке Account? Чому ви намагаєтесь кинути на нього з карти?
Дейв Ньютон,

Для тих з нас, хто може бути незнайомий з бібліотекою, чи можете ви сказати, з якого класу given()статично імпортується цей метод?
Mark Peters

@DaveNewton Account- це модель від Dropwizard, яка використовуєcom.fasterxml.jackson.databind.annotations
Пристрасного інженера

Відповіді:


108

Випуск надходить від Джексона. Коли він не має достатньо інформації про те, до якого класу десеріалізувати, він використовує LinkedHashMap.

Оскільки ви не повідомляєте Джексона про тип елемента вашого ArrayList, він не знає, що ви хочете десериалізувати його ArrayListв Accounts з. Тож воно повертається до типового.

Натомість ви могли б скористатися цим as(JsonNode.class), а потім розібратися з цим ObjectMapperбільш багатим чином, ніж це може бути впевненим. Щось на зразок цього:

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

Ви також можете встановити відповідь якString (), заощаджуючи необхідність робити додаткові перетворення - readValue прийме String як перший аргумент.
BIGDeutsch

@BIGDeutsch: Повторно переглянувши це, мені не потрібно було перетворювати назад на токени там, коли це convertValueможна було зробити за один крок. Перехід до рядка теж працює.
Mark Peters

44

Спробуйте наступне:

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

або:

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

Для отримання додаткової інформації див. Перетворення LinkedHashMap .


3
+1 за показ "convertValue". У мене був випадок, коли мені потрібно було прочитати певну властивість з json як Список, і це саме те, що мені потрібно.
GameSalutes

28

Я міг пом'якшити масив JSON до колекції проблем об'єктів LinkedHashMap , використовуючи, CollectionTypeа не TypeReference. Це те, що я робив і працював :

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

Використовуючи TypeReference, я все ще отримував ArrayList LinkedHashMaps, тобто не працює :

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, різниця у вашому випадку полягала в тому, що сам метод був загальним, тому Tйого не можна було повторно визначити через TypeToken, що, як правило, простіше. Особисто я віддаю перевагу помічник гуави, тому що вона по - , як і раніше типизированная і не специфічна для будь-якого типу контейнера: return new TypeToken<List<T>>(){}.where(new TypeParameter<T>(){}, tClass). Але Джексон не приймає TypeTokens, тому вам тоді потрібно .getType().
Mark Peters

Врятував і мою проблему. Дякую!
Шашанк

6

У мене був подібний виняток (але інша проблема) - java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to org.bson.Documentі, на щастя, це вирішується простіше:

Замість

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

що дає помилку на другому рядку, можна використовувати

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

1

Вирішити проблему за допомогою двох методів синтаксичного розбору

  1. Типом є об’єкт
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. З типом - об'єкт обтікання колекції
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;
}

0

У мене є цей метод для десериалізації XML і перетворення типу:

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

і ось виклик:

List<POJO> pojos = (List<POJO>) MyUtilClass.deserialize(xml, ArrayList.class,new TypeReference< List< POJO >>(){ });

0

Коли ви використовуєте джексон для зіставлення рядка з вашим конкретним класом, особливо якщо ви працюєте із загальним типом. тоді ця проблема може трапитися через завантажувач іншого класу. я зустрів це один раз із сценарієм нижче:

Проект B залежить від бібліотеки A

в бібліотеці А:

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

він має службу запиту даних із зовнішнього джерела та використання джексона для перетворення в конкретний клас

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

у проекті B:

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

і я використовую ServiceA з бібліотеки, щоб робити запити, а також перетворювати дані

public class ServiceAImpl extends ServiceA<Account> {
    
}

і скористатися цим

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

це трапляється тому, що з завантажувача класів LibraryA, jackson не може завантажити клас Account, тоді просто перевизначте метод convertJsonу Project B, щоб дозволити jackson робити свою роботу

public class ServiceAImpl extends ServiceA<Account> {
        @Override
        public DocSearchResponse<T> convertJson(String result){
         return mapper.readValue(result, new TypeReference<DocSearchResponse<T>>() {});
      }
    }
 }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.