Jackson - Deserialisieren mit generischer Klasse

Antworten:

238

Sie müssen TypeReferencefür jeden generischen Typ, den Sie verwenden , ein Objekt erstellen und dieses für die Deserialisierung verwenden. Zum Beispiel -

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});
Eser Aygün
quelle
Ich muss es als TypeReference <Data <T >> () {} verwenden ... Aber ich erhalte die folgende Fehlermeldung - kann nicht über java.lang.class auf private java.lang.class.Class () zugreifen. Fehler beim Festlegen des Zugriffs. Ein java.lang.Class-Konstruktor kann nicht zugänglich gemacht werden
gnjago
Nein, nicht Data<T>, das ist KEIN Typ. Sie müssen die tatsächliche Klasse angeben. sonst ist es dasselbe wie Data<Object>.
StaxMan
18
Was ist, wenn ich bis zur Laufzeit nicht weiß, um welche Klasse es sich handelt? Ich werde die Klasse zur Laufzeit als Parameter erhalten. Wie diese öffentliche <T> void deSerialize (Klasse <T> clazz {ObjectMapper mapper = neuer ObjectMapper (); mapper.readValue (jsonString, neue TypeReference <Json <T>> () {});}
gnjago
1
Ich habe die vollständige Frage hier richtig gestellt stackoverflow.com/questions/11659844/…
gnjago
Wie lautet der vollständige Paketname TypeReference? ist es com.fasterxml.jackson.core.type?
Lei Yang
88

Sie können das nicht tun: Sie müssen einen vollständig aufgelösten Typ wie angeben Data<MyType>. Tist nur eine Variable und ebenso bedeutungslos.

Aber wenn Sie meinen, dass Tdies bekannt sein wird, nur nicht statisch, müssen Sie ein Äquivalent von TypeReferencedynamisch erstellen . Andere Fragen, auf die verwiesen wird, erwähnen dies möglicherweise bereits, aber es sollte ungefähr so ​​aussehen:

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}
StaxMan
quelle
2
Was ist, wenn ich bis zur Laufzeit nicht weiß, um welche Klasse es sich handelt? Ich werde die Klasse zur Laufzeit als Parameter erhalten. Wie diese öffentliche <T> void deSerialize (Klasse <T> clazz {ObjectMapper mapper = neuer ObjectMapper (); mapper.readValue (jsonString, neue TypeReference <Json <T>> () {});}
gnjago
1
Ich habe die vollständige Frage hier gestapelt stackoverflow.com/questions/11659844/…
gnjago
2
Dann bestehen Sie die Klasse einfach so wie sie ist, ohne dass Sie Folgendes benötigen TypeReference: return mapper.readValue(json, clazz);Was genau ist das Problem hier?
StaxMan
2
Das Problem ist, dass "Daten" eine generische Klasse ist. Ich muss angeben, welcher Typ T zur Laufzeit ist. Der Parameter clazz ist das, was wir zur Laufzeit haben. Wie rufe ich readValue auf? Aufrufen mit neuem TypeReference> Json <T>> funktioniert nicht Die vollständige Frage finden Sie hier stackoverflow.com/questions/11659844/…
gnjago
2
OK. Dann müssen Sie verwenden TypeFactory.. Ich werde meine Antwort bearbeiten.
StaxMan
30

Als erstes serialisieren Sie, dann können Sie deserialisieren.
Wenn Sie also serialisieren, sollten Sie verwenden @JsonTypeInfo, damit Jackson Klasseninformationen in Ihre JSON-Daten schreibt. Was Sie tun können, ist wie folgt:

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

Wenn Sie dann deserialisieren, werden Sie feststellen, dass Jackson Ihre Daten in eine Klasse deserialisiert hat, die Ihre Variablen tatsächlich zur Laufzeit treffen.

CharlieQ
quelle
funktioniert nicht, unter Fehler com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Fehlende Typ-ID beim Versuch, den Subtyp [einfacher Typ, Klasse java.lang.Object] aufzulösen: fehlende Typ-ID-Eigenschaft '@class' (für POJO) Eigenschaft 'Daten')
gaurav9620
15

Für Klassendaten <>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)
Devesh
quelle
Dies ist jetzt veraltet.
Evan Gertis
13

Schreiben Sie einfach eine statische Methode in die Util-Klasse. Ich lese einen Json aus einer Datei. Sie können String auch readValue geben

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

Verwendung:

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

Sie können es in eine andere Klasse einschließen, die den Typ Ihres generischen Typs kennt.

Z.B,

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

Hier ist etwas ein konkreter Typ. Sie benötigen einen Wrapper pro reifiziertem Typ. Ansonsten weiß Jackson nicht, welche Objekte er erstellen soll.

sksamuel
quelle
6

JSON-Zeichenfolgen, die deserialisiert werden müssen, müssen die Typinformationen zu Parametern enthalten T.
Sie werden Jackson Anmerkungen müssen auf jeder Klasse setzen , die als Parameter übergeben werden können , Tin der Klasse , Dataso dass die Art Informationen über Parametertyp Tkönnen von / zu JSON - String von Jackson geschrieben gelesen werden.

Nehmen wir an, dass Tdies jede Klasse sein kann, die die abstrakte Klasse erweitert 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 {

}

Sobald jede Klasse (oder ihr gemeinsamer Supertyp), die als Parameter übergeben werden kann, mit T Anmerkungen versehen ist, wird Jackson Informationen zu Parametern Tin den JSON aufnehmen. Ein solcher JSON kann dann deserialisiert werden, ohne den Parameter Tzur Kompilierungszeit zu kennen.
Dieser Jackson-Dokumentationslink befasst sich mit polymorpher Deserialisierung, ist aber auch für diese Frage nützlich.

Sanjeev Sachdev
quelle
und wie schaffe ich das, wenn ich eine Liste haben möchte? Wie sagen wir List <ImageResult>
Luca Archidiacono
5

Ab Jackson 2.5 ist eine elegante Methode zur Lösung dieses Problems die Verwendung der TypeFactory.constructParametricType-Methode (Class parametrized, Class ... parameterClasses) , mit der ein Jackson JavaTypedurch Angabe der parametrisierten Klasse und ihrer parametrisierten Typen direkt definiert werden kann.

Angenommen, Sie möchten deserialisieren Data<String>, können Sie Folgendes tun:

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

Beachten Sie, dass es nicht wirklich schwieriger wäre, wenn die Klasse mehrere parametrisierte Typen deklarieren würde:

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

Wir könnten :

JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);
davidxxx
quelle
Super, danke, es hat bei mir funktioniert. Mit der Typreferenz habe ich eine Classcast-Ausnahme von der Zuordnung zum spezifischen Objekt erhalten, aber das macht wirklich den Job.
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;
    }
    ...
 }
mikka22
quelle
0

Wenn Sie Scala verwenden und den generischen Typ zur Kompilierungszeit kennen, TypeReference jedoch nicht überall in all Ihren API-Anwendern manuell übergeben möchten, können Sie den folgenden Code verwenden (mit 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)
  }

die so verwendet werden kann:

read[List[Map[Int, SomethingToSerialize]]](inputStream)
nathan g
quelle
0

Um eine generische JSON-Zeichenfolge mit Jackson in ein Java-Objekt zu deserialisieren, benötigen Sie:

  1. So definieren Sie eine JSON-Klasse

  2. Führen Sie eine Attributzuordnung durch.

Endgültiger Code, getestet und einsatzbereit:

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

Wichtig:
@JsonAnySetter Annotation ist obligatorisch und gewährleistet eine generische JSON-Analyse und -Population.

Für kompliziertere Fälle mit verschachtelten Arrays lesen Sie bitte die Baeldung-Referenz: https://www.baeldung.com/jackson-mapping-dynamic-object

Mike B.
quelle
0

Beispiel für eine nicht sehr gute, aber einfache Entscheidung (nicht nur für Jackson, sondern auch für Spring RestTemplate usw.):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Demel
quelle