Jackson y referencia de tipo genérico

107

Quiero usar la biblioteca jackson json para un método genérico de la siguiente manera:

public MyRequest<T> tester() {
    TypeReference<MyWrapper<T>> typeRef = new TypeReference<MyWrapper<T>>();  
    MyWrapper<T> requestWrapper = (MyWrapper<T>) JsonConverter.fromJson(jsonRequest, typeRef);
    return requestWrapper.getRequest();
}

...

public class MyWrapper<T> {

    private MyRequest<T> request;

    public MyRequest<T> getRequest() {
        return request;
    }

    public void setRequest(MyRequest<T> request) {
        this.request = request;
    }
}


 public class MyRequest{
     private List<T> myobjects;

     public void setMyObjects(List<T> ets) {
         this.myobjects = ets;
     }

     @NotNull
     @JsonIgnore
     public T getMyObject() {
         return myobjects.get(0);
     }
}

Ahora el problema es que cuando llamo a getMyObject () que está dentro del objeto de solicitud, jackson devuelve el objeto personalizado anidado como LinkedHashMap. ¿Hay alguna forma en la que especifique que el objeto T debe devolverse? Por ejemplo: si envié un objeto de tipo Cliente, ¿el Cliente debería ser devuelto de esa Lista ?.

Gracias.

Techzen
fuente
Agregue la implementación de getT ()
Jim Garrison
Esta pregunta es similar a stackoverflow.com/questions/6062011/… pero han sugerido especificar el tipo usando TypeFactory. Sin embargo, no sé el tipo en tiempo de compilación ...
techzen
TypeFactory tiene métodos que no necesitan clases estáticas; createCollectionType y así sucesivamente.
StaxMan
Comparta el código completo. También me enfrento al mismo problema.
AZ_
¿No es TypeReferenceabstracto?
Kyle Delaney

Respuestas:

194

Este es un problema bien conocido con el borrado de tipo Java: T es solo una variable de tipo y debe indicar la clase real, generalmente como argumento de clase. Sin esa información, lo mejor que se puede hacer es utilizar límites; y T simple es aproximadamente lo mismo que "T extiende el objeto". Y Jackson luego vinculará objetos JSON como mapas.

En este caso, el método de prueba debe tener acceso a la clase y puede construir

JavaType type = mapper.getTypeFactory().
  constructCollectionType(List.class, Foo.class)

y entonces

List<Foo> list = mapper.readValue(new File("input.json"), type);
StaxMan
fuente
16
Funciona: Hice lo siguiente: JavaType topMost = mapper.getTypeFactory (). ConstructParametricType (MyWrapper.class, ActualClassRuntime.class); y luego hizo readValue y finalmente funcionó :)
techzen
Sí, funciona, gracias por señalar el método para crear un tipo genérico que no sea el tipo Mapa / Colección.
StaxMan
1
@StaxMan ¿sería mejor usar ClassMate para este tipo de cosas a partir de ahora?
Husayt
2
@husayt sí, técnicamente java-classmate lib es superior. Pero integrarlo con Jackson es un poco complicado solo porque la abstracción de tipos de Jackson es parte integrada de API. A largo plazo, sería genial encontrar la manera adecuada de hacer que Jackson use el código de compañero de clase, ya sea integrado o mediante dep.
StaxMan
1
Siento que Jackson no debería tener que cubrir lo que se sienten como brechas en los genéricos, pero de cualquier manera, lo hace muy bien.
Adrian Baker
6

¡¡'JavaType' funciona !! Estaba tratando de deshacer (deserializar) una Lista en json String para ArrayList java Objects y estaba luchando por encontrar una solución desde hace días.
A continuación se muestra el código que finalmente me dio la solución. Código:

JsonMarshallerUnmarshaller<T> {
    T targetClass;

    public ArrayList<T> unmarshal(String jsonString) {
        ObjectMapper mapper = new ObjectMapper();

        AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
        mapper.getDeserializationConfig()
            .withAnnotationIntrospector(introspector);

        mapper.getSerializationConfig()
            .withAnnotationIntrospector(introspector);
        JavaType type = mapper.getTypeFactory().
            constructCollectionType(
                ArrayList.class, 
                targetclass.getClass());

        try {
            Class c1 = this.targetclass.getClass();
            Class c2 = this.targetclass1.getClass();
            ArrayList<T> temp = (ArrayList<T>) 
                mapper.readValue(jsonString,  type);
            return temp ;
        } catch (JsonParseException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null ;
    }  
}
Rushidesai1
fuente
¿Cómo inicializar TargetClass?
AZ_
Muéstrame un pequeño ejemplo. Estoy pasando Class <?> Target y luego obtengo target.getClassName ().
AZ_
1
Agregue un constructor de la siguiente manera: JsonMarshallerUnmarshaller <T> {private Class <T> targetClass; JsonMarshallerUnmarshaller (Clase <T> c) {targetClass = c; }} Realice los cambios apropiados ahora a la función 'unmarshal' para usar esta clase en lugar de hacer getClass en todas partes.
rushidesai1
Un par de notas: el código se puede simplificar mucho al señalar que todas las excepciones son subtipos de IOException(solo necesita una captura), y que el introspector de anotaciones predeterminado ya JacksonAnnotationIntrospectorlo es , por lo que no es necesario hacer nada ObjectMapper, solo constrúyalo y funciona.
StaxMan
Así que este código ni siquiera puedo compilarlo. ¿Tienes algún ejemplo en vivo para pegar en su lugar?
Llave inglesa
0

He modificado respuesta de rushidesai1 para incluir un ejemplo de trabajo.

JsonMarshaller.java

import java.io.*;
import java.util.*;

public class JsonMarshaller<T> {
    private static ClassLoader loader = JsonMarshaller.class.getClassLoader();

    public static void main(String[] args) {
        try {
            JsonMarshallerUnmarshaller<Station> marshaller = new JsonMarshallerUnmarshaller<>(Station.class);
            String jsonString = read(loader.getResourceAsStream("data.json"));
            List<Station> stations = marshaller.unmarshal(jsonString);
            stations.forEach(System.out::println);
            System.out.println(marshaller.marshal(stations));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("resource")
    public static String read(InputStream ios) {
        return new Scanner(ios).useDelimiter("\\A").next(); // Read the entire file
    }
}

Salida

Station [id=123, title=my title, name=my name]
Station [id=456, title=my title 2, name=my name 2]
[{"id":123,"title":"my title","name":"my name"},{"id":456,"title":"my title 2","name":"my name 2"}]

JsonMarshallerUnmarshaller.java

import java.io.*;
import java.util.List;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;

public class JsonMarshallerUnmarshaller<T> {
    private ObjectMapper mapper;
    private Class<T> targetClass;

    public JsonMarshallerUnmarshaller(Class<T> targetClass) {
        AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();

        mapper = new ObjectMapper();
        mapper.getDeserializationConfig().with(introspector);
        mapper.getSerializationConfig().with(introspector);

        this.targetClass = targetClass;
    }

    public List<T> unmarshal(String jsonString) throws JsonParseException, JsonMappingException, IOException {
        return parseList(jsonString, mapper, targetClass);
    }

    public String marshal(List<T> list) throws JsonProcessingException {
        return mapper.writeValueAsString(list);
    }

    public static <E> List<E> parseList(String str, ObjectMapper mapper, Class<E> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return mapper.readValue(str, listType(mapper, clazz));
    }

    public static <E> List<E> parseList(InputStream is, ObjectMapper mapper, Class<E> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return mapper.readValue(is, listType(mapper, clazz));
    }

    public static <E> JavaType listType(ObjectMapper mapper, Class<E> clazz) {
        return mapper.getTypeFactory().constructCollectionType(List.class, clazz);
    }
}

Station.java

public class Station {
    private long id;
    private String title;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return String.format("Station [id=%s, title=%s, name=%s]", id, title, name);
    }
}

data.json

[{
  "id": 123,
  "title": "my title",
  "name": "my name"
}, {
  "id": 456,
  "title": "my title 2",
  "name": "my name 2"
}]
Sr. Polywhirl
fuente