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

88

Я получаю ошибку ниже:

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 также добавил всю трассировку стека
Passionate Engineer
2
Что такое Account? Почему вы пытаетесь выполнить трансляцию с карты?
Дэйв Ньютон
Для тех из нас, кто может быть не знаком с библиотекой, можете ли вы сказать, из какого класса given()статически импортируется этот метод?
Марк Питерс
@DaveNewton Account- это модель от Dropwizard, которая используетcom.fasterxml.jackson.databind.annotations
Passionate Engineer

Ответы:

111

Проблема исходит от Джексона. Когда у него недостаточно информации о том, в какой класс десериализоваться, он использует 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);
Марк Питерс
источник
Вы также можете установить ответ asString (), что избавит от необходимости выполнять дополнительные преобразования - readValue примет String в качестве первого аргумента.
BIGDeutsch
@BIGDeutsch: Просто вернувшись к этому, мне не нужно было конвертировать обратно в токены, когда я convertValueмог сделать это за один шаг. Переход к строке тоже работает.
Марк Питерс
45

Попробуйте следующее:

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

или же:

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

См. Преобразование LinkedHashMap для получения дополнительной информации.

user2578871
источник
3
+1 за отображение convertValue. У меня был случай, когда мне нужно было прочитать определенное свойство из json в виде списка, и это именно то, что мне нужно.
GameSalutes
31

Я мог смягчить проблему с массивом 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). Но Джексон не принимает TypeTokenтак, что вам тогда нужно .getType().
Марк Петерс
Спасла и мою проблему. Спасибо!
Shashank
Спасибо! Потратил много времени, чтобы исправить эту ошибку, пока не нашел ответ !!
glco
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));
Nurb
источник
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;
}
Люблю Java
источник
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

в библиотеке 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>>() {});
  }
}

в Проекте Б:

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, Джексон не может загрузить класс Account, а затем просто переопределить метод convertJsonв Project B, чтобы позволить Джексону выполнять свою работу

public class ServiceAImpl extends ServiceA<Account> {
        @Override
        public DocSearchResponse<T> convertJson(String result){
         return mapper.readValue(result, new TypeReference<DocSearchResponse<T>>() {});
      }
    }
 }
дядя Боб
источник