Jackson - Deserializza usando una classe generica


147

Ho una stringa JSON, che dovrei deserializzare alla seguente classe

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

Come lo faccio? Questo è il solito modo

mapper.readValue(jsonString, Data.class);

Ma come posso dire cosa rappresenta T?



Risposte:


238

È necessario creare un TypeReferenceoggetto per ogni tipo generico che si utilizza e utilizzarlo per la deserializzazione. Per esempio -

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

Devo usarlo come TypeReference <Data <T>> () {} ... Ma sto ottenendo il seguente errore: impossibile accedere a java.lang.class.Class () privato da java.lang.class. Impossibile impostare l'accesso. Impossibile rendere accessibile un costruttore java.lang.Class
gnjago

No, Data<T>NON è un tipo. È necessario specificare la classe effettiva; altrimenti è lo stesso di Data<Object>.
StaxMan,

18
Cosa succede se non so che classe è fino al runtime? Otterrò la classe come parametro durante il runtime. Come questo pubblico <T> void deSerialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago

1
Ho chiesto al completo domanda corretta qui stackoverflow.com/questions/11659844/...
gnjago

qual è il nome del pacchetto completo di TypeReference? è com.fasterxml.jackson.core.type?
Lei Yang,

88

Non puoi farlo: devi specificare il tipo completamente risolto, come Data<MyType>. Tè solo una variabile e come è insignificante.

Ma se vuoi dire che Tsarà noto, ma non staticamente, devi creare un equivalente di TypeReferencedinamicamente. Altre domande citate potrebbero già menzionarlo, ma dovrebbe assomigliare a:

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

2
Cosa succede se non so che classe è fino al runtime? Otterrò la classe come parametro durante il runtime. Come questo pubblico <T> void deSerialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago

1
Ho chiesto al completo domanda qui stackoverflow.com/questions/11659844/...
gnjago

2
Quindi passa la classe così com'è, non c'è bisogno di TypeReference: return mapper.readValue(json, clazz);qual è esattamente il problema qui?
StaxMan,

2
Il problema è che "Dati" è una classe generica. Devo specificare quale tipo T è in fase di esecuzione. Il parametro clazz è ciò che ci usa in fase di esecuzione. Quindi, come chiamare readValue? chiamandola con il nuovo TypeReference> JSON <T >> non funziona La piena questione è qui stackoverflow.com/questions/11659844/...~~V~~singular~~3rd
gnjago

2
Ok. Quindi devi usare TypeFactory.. Modificherò la mia risposta.
StaxMan,

30

La prima cosa che fai è serializzare, quindi puoi deserializzare.
quindi quando serializzi, dovresti usare @JsonTypeInfoper lasciare che jackson scriva le informazioni sulla classe nei tuoi dati json. Quello che puoi fare è così:

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

Quindi quando deserializzi, troverai che Jackson ha deserializzato i tuoi dati in una classe in cui i tuoi hit variabili sono effettivamente in fase di esecuzione.


non funziona, scendendo sotto l'errore com.fasterxml.jackson.databind.exc.InvalidTypeIdException: ID di tipo mancante quando si cerca di risolvere il sottotipo di [tipo semplice, classe java.lang.Object]: proprietà dell'ID di tipo mancante '@class' (per POJO proprietà 'dati')
gaurav9620

15

Per dati di classe <>

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

Questo è ora deprecato.
Evan Gertis,

13

Basta scrivere un metodo statico nella classe Util. Sto leggendo un Json da un file. puoi dare String anche 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

Puoi avvolgerlo in un'altra classe che conosce il tipo del tuo tipo generico.

Per esempio,

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

Qui Something è un tipo concreto. È necessario un wrapper per tipo reificato. Altrimenti Jackson non sa quali oggetti creare.


6

La stringa JSON che deve essere deserializzata dovrà contenere le informazioni sul tipo relative al parametro T.
Dovrai mettere le annotazioni di Jackson su ogni classe che può essere passata come parametro Ta classe in Datamodo che le informazioni sul tipo sul tipo di parametro Tpossano essere lette / scritte nella stringa JSON da Jackson.

Supponiamo che Tpossa essere qualsiasi classe che estende la classe astratta 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 volta T annotata ciascuna delle classi (o il loro supertipo comune) che può essere passato come parametro , Jackson includerà le informazioni sui parametri Tnel JSON. Tale JSON può quindi essere deserializzato senza conoscere il parametro Tal momento della compilazione.
Questo collegamento alla documentazione di Jackson parla della deserializzazione polimorfica, ma è utile fare riferimento anche per questa domanda.


e come posso gestirlo se voglio avere un elenco? Come diciamo Elenco <ImageResult>
Luca Archidiacono

5

Da Jackson 2.5, un modo elegante di risoluzione che sta usando il metodo TypeFactory.constructParametricType (Class parametrized, Class ... parameterClasses) che consente di definire con precisione un Jackson JavaTypespecificando la classe con parametri e i suoi tipi con parametri.

Supponendo di voler deserializzare Data<String>, puoi fare:

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

Nota che se la classe dichiarasse più tipi con parametri, non sarebbe molto più difficile:

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

Potremmo fare:

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

Fantastico, grazie ha funzionato per me. Con la typereference ho ottenuto l'eccezione del classcast dalla mappa all'oggetto specifico, ma questo fa davvero il lavoro.
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

se stai usando scala e conosci il tipo generico al momento della compilazione, ma non vuoi passare manualmente TypeReference ovunque in tutti i tuoi apri, puoi usare il seguente codice (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)
  }

che può essere usato in questo modo:

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

0

Per deserializzare una stringa JSON generica in un oggetto Java con Jackson è necessario:

  1. Per definire una classe JSON.

  2. Eseguire una mappatura degli attributi.

Codice finale, testato e pronto per essere utilizzato:

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: l'
@JsonAnySetter annotazione è obbligatoria, garantisce un'analisi e una popolazione JSON generiche.

Per casi più complicati con array nidificati, consultare il riferimento Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object


0

Esempio di decisione non molto buona, ma semplice (non solo per Jackson, anche per Spring RestTemplate, ecc.):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.