java.lang.ClassCastException: java.util.LinkedHashMap no se puede convertir a com.testing.models.Account

84

Me aparece el siguiente error:

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

con el siguiente código

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

¿Hay alguna razón por la que no pueda hacerlo get(0)?

Ingeniero apasionado
fuente
¿No se puede lanzar a qué ? ¿Cuál es el resto del mensaje de error?
Oliver Charlesworth
1
@OliverCharlesworth también agregó stacktrace completo
Ingeniero apasionado
2
¿Qué es un Account? ¿Por qué intentas enviar contenido desde un mapa?
Dave Newton
Para aquellos de nosotros que no estemos familiarizados con la biblioteca, ¿pueden decirnos de qué clase given()se importa este método estáticamente?
Mark Peters
@DaveNewton Accountes un modelo de Dropwizard que usacom.fasterxml.jackson.databind.annotations
Ingeniero apasionado

Respuestas:

108

El problema viene de Jackson. Cuando no tiene suficiente información sobre a qué clase deserializar, usa LinkedHashMap.

Dado que no le informa a Jackson el tipo de elemento de su ArrayList, no sabe que desea deserializar en una ArrayListde Accounts. Entonces vuelve al valor predeterminado.

En su lugar, probablemente podría usar as(JsonNode.class)y luego lidiar con el ObjectMapperde una manera más rica de lo que permite la tranquilidad. Algo como esto:

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
fuente
También puede configurar la respuesta comoString (), ahorra tener que hacer conversiones adicionales - readValue aceptará una Cadena como primer argumento.
BIGDeutsch
@BIGDeutsch: Solo revisando esto, no había necesidad de que volviera a convertir a tokens allí cuando convertValuepodía hacerlo en un solo paso. Ir a una cadena también funciona.
Mark Peters
44

Intente lo siguiente:

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

o:

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

Consulte la conversión de LinkedHashMap para obtener más información.

usuario2578871
fuente
3
+1 por mostrar "convertValue". Tuve un caso en el que necesitaba leer una propiedad particular de json como una lista y esto es exactamente lo que necesitaba.
GameSalutes
28

La forma en que pude mitigar el problema de JSON Array a la colección de objetos LinkedHashMap fue usando en CollectionTypelugar de un TypeReference. Esto es lo que hice y trabajé :

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

Usando el TypeReference, todavía estaba obteniendo un ArrayList de LinkedHashMaps, es decir , no funciona :

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;
}
Pantalón Himadri
fuente
1
También salvó mi problema. ¡Gracias!
Vladimir Gilevich
1
+1, la diferencia en su caso fue que el método en sí era genérico, por lo Tque no se podía cosificar a través de TypeToken, que suele ser más fácil. Personalmente prefiero el ayudante de guayaba porque todavía typesafe y no es específica a cualquier tipo de contenedor: return new TypeToken<List<T>>(){}.where(new TypeParameter<T>(){}, tClass). Pero Jackson no toma TypeTokens, entonces tú necesitas .getType().
Mark Peters
También salvó mi problema. ¡Gracias!
Shashank
6

Tuve una excepción similar (pero un problema diferente) java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to org.bson.Document, y afortunadamente se resuelve más fácilmente:

En vez de

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

que da error en la segunda línea, se puede usar

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

Resolver problema con dos métodos de análisis común

  1. Con tipo es un objeto
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. Con tipo es objeto de envoltura de colección
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;
}
Me encanta Java
fuente
0

Tengo este método para deserializar un XML y convertir el tipo:

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

y esta es la llamada:

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

Cuando usa jackson para mapear desde una cadena a su clase concreta, especialmente si trabaja con tipo genérico. entonces este problema puede ocurrir debido a un cargador de clases diferente. Lo conocí una vez con el siguiente escenario:

El proyecto B depende de la biblioteca A

en la biblioteca A:

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

tiene servicio para consultar datos de una fuente externa y usa jackson para convertir a una clase concreta

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

en el Proyecto B:

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

y uso ServiceA de la biblioteca para realizar consultas y también convertir datos

public class ServiceAImpl extends ServiceA<Account> {
    
}

y hacer uso de eso

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

sucede porque desde el cargador de clases de LibraryA, jackson no puede cargar la clase de cuenta, luego simplemente anula el método convertJsonen el proyecto B para dejar que jackson haga su trabajo

public class ServiceAImpl extends ServiceA<Account> {
        @Override
        public DocSearchResponse<T> convertJson(String result){
         return mapper.readValue(result, new TypeReference<DocSearchResponse<T>>() {});
      }
    }
 }
tío Bob
fuente