java.lang.ClassCastException: java.util.LinkedHashMap nie można rzutować na com.testing.models.Account

88

Otrzymuję poniższy błąd:

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

z poniższym kodem

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

Czy istnieje powód, dla którego nie mogę tego zrobić get(0)?

Inżynier z pasją
źródło
Nie można rzucić na co ? Jaka jest reszta komunikatu o błędzie?
Oliver Charlesworth
1
@OliverCharlesworth dodał także cały ślad stosu
Namiętny inżynier
2
Co to jest Account? Dlaczego próbujesz przesyłać do niego z mapy?
Dave Newton
Dla tych z nas, którzy mogą nie być zaznajomieni z biblioteką, czy możesz powiedzieć, z jakiej klasy ta given()metoda jest statycznie importowana?
Mark Peters
@DaveNewton Accountto model z Dropwizard, który używacom.fasterxml.jackson.databind.annotations
Passionate Engineer

Odpowiedzi:

112

Problem pochodzi od Jacksona. Gdy nie ma wystarczających informacji na temat klasy do deserializacji, używa LinkedHashMap.

Ponieważ nie informujesz Jacksona o typie swojego elementu ArrayList, nie wie, że chcesz deserializować się na an ArrayListz Accounts. Więc wraca do ustawień domyślnych.

Zamiast tego prawdopodobnie mógłbyś użyć as(JsonNode.class), a następnie poradzić sobie z nimi ObjectMapperw bogatszy sposób, niż pozwala na to pewność. Coś takiego:

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);
Mark Peters
źródło
Możesz również ustawić odpowiedź asString (), oszczędza konieczności wykonywania dodatkowych konwersji - readValue przyjmie String jako pierwszy argument.
BIGDeutsch
@BIGDeutsch: Wracając do tego, nie było potrzeby, abym zamienił się z powrotem na tokeny, kiedy convertValuemógłbym to zrobić w jednym kroku. Przechodzenie do struny też działa.
Mark Peters
45

Spróbuj wykonać następujące czynności:

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

lub:

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

Aby uzyskać więcej informacji, zobacz konwersję LinkedHashMap .

user2578871
źródło
3
+1 za wyświetlenie „ConvertValue”. Miałem przypadek, w którym musiałem odczytać konkretną właściwość z json jako listę i właśnie tego potrzebowałem.
GameSalutes,
31

Sposób, w jaki mogłem złagodzić problem z tablicą JSON w celu zbierania obiektów LinkedHashMap, polegał na użyciu CollectionTypezamiast pliku TypeReference. Oto, co zrobiłem i pracowałem :

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

Używając TypeReference, nadal otrzymywałem ArrayList of LinkedHashMaps, tj. Nie działa :

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;
}
Himadri Pant
źródło
1
Uratowałem również mój problem. Dziękuję Ci!
Vladimir Gilevich
1
+1, różnica w twoim przypadku polegała na tym, że sama metoda była ogólna, więc Tnie można jej było zreifikować za pomocą TypeToken, co jest zwykle łatwiejsze. Osobiście wolę pomocnika guawy, bo to wciąż typesafe i nie specyficzne dla każdego rodzaju pojemnika: return new TypeToken<List<T>>(){}.where(new TypeParameter<T>(){}, tClass). Ale Jackson nie bierze TypeTokentego, więc potrzebujesz .getType().
Mark Peters
Uratowałem również mój problem. Dziękuję Ci!
Shashank,
Dziękuję Ci! Spędziłem dużo czasu, aby naprawić ten błąd, dopóki nie znalazłem tej odpowiedzi!
glco
6

Miałem podobny wyjątek (ale inny problem) - java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to org.bson.Documenti na szczęście łatwiej go rozwiązać:

Zamiast

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

co daje błąd w drugiej linii, można użyć

List<Document> docs = obj.get("documents");
Document doc = new Document(docs.get(0));
nurb
źródło
1

Rozwiąż problem za pomocą dwóch wspólnych metod analizy

  1. Rodzaj jest obiektem
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. Typ jest obiektem zawijania kolekcji
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;
}
Uwielbiam Javę
źródło
0

Mam tę metodę deserializacji XML i konwertowania typu:

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

a to jest wezwanie:

List<POJO> pojos = (List<POJO>) MyUtilClass.deserialize(xml, ArrayList.class,new TypeReference< List< POJO >>(){ });
Mohamed Farghaly
źródło
0

Kiedy używasz jacksona do mapowania z ciągu znaków na konkretną klasę, zwłaszcza jeśli pracujesz z typem ogólnym. wtedy ten problem może wystąpić z powodu innego programu ładującego klasy. spotkałem to kiedyś z poniższym scenariuszem:

Projekt B zależy od Biblioteki A

w bibliotece A:

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

ma usługę do przeszukiwania danych z zewnętrznego źródła i używa jsona do konwersji na konkretną klasę

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

w Projekcie B:

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

i używam ServiceA z biblioteki do tworzenia zapytań i konwersji danych

public class ServiceAImpl extends ServiceA<Account> {
    
}

i wykorzystaj to

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

dzieje się tak, ponieważ z modułu ładującego klasy biblioteki A, jackson nie może załadować klasy konta, a następnie po prostu nadpisuje metodę convertJsonw projekcie B, aby pozwolić jacksonowi wykonać swoją pracę

public class ServiceAImpl extends ServiceA<Account> {
        @Override
        public DocSearchResponse<T> convertJson(String result){
         return mapper.readValue(result, new TypeReference<DocSearchResponse<T>>() {});
      }
    }
 }
wujek bob
źródło