Jackson - Deserialisieren mit generischer Klasse


147

Ich habe eine JSON-Zeichenfolge, die ich in die folgende Klasse deSerialisieren sollte

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

Wie mache ich es? Dies ist der übliche Weg

mapper.readValue(jsonString, Data.class);

Aber wie erwähne ich, wofür T steht?



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

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

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.


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)

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

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.


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.


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

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

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)

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


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());
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.