Jackson - Deserializar usando una clase genérica


Respuestas:


238

Debe crear un TypeReferenceobjeto para cada tipo genérico que use y usarlo para la deserialización. Por ejemplo -

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});

Tengo que usarlo como TypeReference <Data <T>> () {} ... Pero recibo el siguiente error: no puedo acceder al java.lang.class.Class () privado de java.lang.class. Error al establecer el acceso. No se puede hacer accesible un constructor java.lang.Class
gnjago

No, no Data<T>, eso NO es un tipo. Debe especificar la clase real; de lo contrario es lo mismo que Data<Object>.
StaxMan

18
¿Qué pasa si no sé qué clase es hasta el tiempo de ejecución? Obtendré la clase como parámetro durante el tiempo de ejecución. Al igual que este <T> void deSerialize público (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago

1
He hecho la pregunta completa correctamente aquí stackoverflow.com/questions/11659844/…
gnjago

¿Cuál es el nombre completo del paquete TypeReference? ¿es com.fasterxml.jackson.core.type?
Lei Yang

88

No puede hacer eso: debe especificar el tipo totalmente resuelto, como Data<MyType>. Tes solo una variable, y como no tiene sentido.

Pero si quiere decir que Tse sabrá, pero no estáticamente, debe crear un equivalente de TypeReferenceforma dinámica. Otras preguntas a las que se hace referencia ya pueden mencionar esto, pero debería verse así:

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}

2
¿Qué pasa si no sé qué clase es hasta el tiempo de ejecución? Obtendré la clase como parámetro durante el tiempo de ejecución. Como este <T> void deSerialize público (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago

1
He hecho la pregunta completa aquí stackoverflow.com/questions/11659844/…
gnjago

2
Luego, simplemente pase la clase como está, sin necesidad de TypeReference: return mapper.readValue(json, clazz);¿Cuál es exactamente el problema aquí?
StaxMan

2
El problema es que "Datos" es una clase genérica. Necesito especificar qué tipo T es en tiempo de ejecución. El parámetro clazz es lo que usamos en tiempo de ejecución. Entonces, ¿cómo llamar a readValue? llamarlo con la nueva TypeReference> Json <T>> no funciona La pregunta completa está aquí stackoverflow.com/questions/11659844/…
gnjago

2
Okay. Entonces necesitas usar TypeFactory... Editaré mi respuesta.
StaxMan

30

Lo primero que debe hacer es serializar, luego puede deserializar.
así que cuando serialices, deberías usar @JsonTypeInfopara dejar que Jackson escriba información de clase en tus datos json Lo que puedes hacer es así:

Class Data <T> {
    int found;
    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    Class<T> hits
}

Luego, cuando deserialice, encontrará que Jackson ha deserializado sus datos en una clase en la que sus aciertos variables están en tiempo de ejecución.


no funciona, obteniendo el siguiente error com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Falta el tipo de identificación al intentar resolver el subtipo de [tipo simple, clase java.lang.Object]: falta la propiedad de identificación de tipo '@clase' (para POJO propiedad 'datos')
gaurav9620

15

Para la clase Datos <>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)

Esto ahora está en desuso.
Evan Gertis

13

Simplemente escriba un método estático en la clase Util. Estoy leyendo un Json de un archivo. puedes darle String también a readValue

public static <T> T convertJsonToPOJO(String filePath, Class<?> target) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(new File(filePath), objectMapper .getTypeFactory().constructCollectionType(List.class, Class.forName(target.getName())));
}

Uso:

List<TaskBean> list =  Util.<List<TaskBean>>convertJsonToPOJO("E:/J2eeWorkspaces/az_workspace_svn/az-client-service/dir1/dir2/filename.json", TaskBean.class);

7

Puede envolverlo en otra clase que conozca el tipo de su tipo genérico.

P.ej,

class Wrapper {
 private Data<Something> data;
}
mapper.readValue(jsonString, Wrapper.class);

Aquí algo es un tipo concreto. Necesita un contenedor por tipo reificado. De lo contrario, Jackson no sabe qué objetos crear.


6

La cadena JSON que debe ser deserializada tendrá que contener la información de tipo sobre el parámetro T.
Tendrá que poner anotaciones de Jackson en cada clase que se pueda pasar como parámetro Ta clase Datapara que TJackson pueda leer / escribir la información del tipo sobre el tipo de parámetro .

Supongamos que Tpuede ser cualquier clase que extienda la clase abstracta Result.

class Data <T extends Result> {
    int found;
    Class<T> hits
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ImageResult.class, name = "ImageResult"),
        @JsonSubTypes.Type(value = NewsResult.class, name = "NewsResult")})
public abstract class Result {

}

public class ImageResult extends Result {

}

public class NewsResult extends Result {

}

Una vez que T se anota cada clase (o su supertipo común) que se puede pasar como parámetro , Jackson incluirá información sobre el parámetro Ten el JSON. Tal JSON se puede deserializar sin conocer el parámetro Ten tiempo de compilación.
Este enlace de documentación de Jackson habla sobre la deserialización polimórfica, pero también es útil hacer referencia a esta pregunta.


¿Y cómo gestiono esto si quiero tener una Lista? Como digamos List <ImageResult>
Luca Archidiacono

5

Desde Jackson 2.5, una forma elegante de resolver usando el método TypeFactory.constructParametricType (Class parametrized, Class ... parameterClasses) que permite definir de forma recta un Jackson JavaTypeal especificar la clase parametrizada y sus tipos parametrizados.

Suponiendo que desea deserializarse Data<String>, puede hacer:

// the json variable may be a String, an InputStream and so for...
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class);
Data<String> data = mapper.readValue(json, type);

Tenga en cuenta que si la clase declara múltiples tipos parametrizados, no sería realmente más difícil:

class Data <T, U> {
    int found;
    Class<T> hits;
    List<U> list;
}

Podríamos hacer :

JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);

Impresionante, gracias funcionó para mí. Con la referencia de tipo, obtuve una excepción de difusión de clase del mapa al objeto específico, pero esto realmente hace el trabajo.
Tacsiazuma

1
public class Data<T> extends JsonDeserializer implements ContextualDeserializer {
    private Class<T> cls;
    public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty prop) throws JsonMappingException {
        cls = (Class<T>) ctx.getContextualType().getRawClass();
        return this;
    }
    ...
 }

0

si está usando scala y conoce el tipo genérico en tiempo de compilación, pero no desea pasar manualmente TypeReference a todas partes en todas sus aplicaciones, puede usar el siguiente código (con jackson 2.9.5):

def read[T](entityStream: InputStream)(implicit typeTag: WeakTypeTag[T]): T = {

    //nathang: all of this *crazy* scala reflection allows us to handle List[Seq[Map[Int,Value]]]] without passing
    // new TypeReference[List[Seq[Map[Int,Value]]]]](){} to the function

    def recursiveFindGenericClasses(t: Type): JavaType = {
      val current = typeTag.mirror.runtimeClass(t)

      if (t.typeArgs.isEmpty) {
        val noSubtypes = Seq.empty[Class[_]]
        factory.constructParametricType(current, noSubtypes:_*)
      }

      else {
        val genericSubtypes: Seq[JavaType] = t.typeArgs.map(recursiveFindGenericClasses)
        factory.constructParametricType(current, genericSubtypes:_*)
      }

    }

    val javaType = recursiveFindGenericClasses(typeTag.tpe)

    json.readValue[T](entityStream, javaType)
  }

que se puede usar así:

read[List[Map[Int, SomethingToSerialize]]](inputStream)

0

Para deserializar una cadena JSON genérica a un objeto Java con Jackson, necesita:

  1. Para definir una clase JSON.

  2. Realizar un mapeo de atributos.

Código final, probado y listo para usar:

static class MyJSON {

    private Map<String, Object> content = new HashMap<>();

    @JsonAnySetter
    public void setContent(String key, Object value) {
        content.put(key, value);
    }
}

String json = "{\"City\":\"Prague\"}";

try {

    MyPOJO myPOJO = objectMapper.readValue(json, MyPOJO.class);

    String jsonAttVal = myPOJO.content.get("City").toString();

    System.out.println(jsonAttVal);

} catch (IOException e) {
    e.printStackTrace();
}

Importante: la
@JsonAnySetter anotación es obligatoria, garantiza un análisis genérico de JSON y una población.

Para casos más complicados con matrices anidadas, consulte la referencia de Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object


0

Ejemplo de decisión no muy buena, pero simple (no solo para Jackson, también para Spring RestTemplate, etc.):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.