Jackson und generische Typreferenz

107

Ich möchte die Jackson JSON-Bibliothek für eine generische Methode wie folgt verwenden:

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

Das Problem ist nun, dass wenn ich getMyObject () aufrufe, das sich innerhalb des Anforderungsobjekts befindet, Jackson das verschachtelte benutzerdefinierte Objekt als LinkedHashMap zurückgibt. Gibt es eine Möglichkeit, wie ich spezifiziere, dass das T-Objekt zurückgegeben werden muss? Beispiel: Wenn ich ein Objekt vom Typ Kunde gesendet habe, sollte der Kunde von dieser Liste zurückgegeben werden.

Vielen Dank.

techzen
quelle
Bitte fügen Sie die Implementierung von getT ()
Jim Garrison
Diese Frage ähnelt stackoverflow.com/questions/6062011/… , es wurde jedoch vorgeschlagen, den Typ mithilfe von TypeFactory anzugeben. Allerdings kenne ich den Typ zur Kompilierungszeit nicht ...
Techzen
TypeFactory verfügt über Methoden, die keine statische Klasse benötigen. createCollectionType und so weiter.
StaxMan
Bitte teilen Sie den vollständigen Code. Ich stehe auch vor dem gleichen Problem.
AZ_
Ist nicht TypeReferenceabstrakt?
Kyle Delaney

Antworten:

194

Dies ist ein bekanntes Problem beim Löschen von Java-Typen: T ist nur eine Typvariable, und Sie müssen die tatsächliche Klasse angeben, normalerweise als Klassenargument. Ohne solche Informationen ist es am besten, Grenzen zu verwenden. und einfaches T ist ungefähr dasselbe wie 'T erweitert Objekt'. Und Jackson wird dann JSON-Objekte als Karten binden.

In diesem Fall muss die Testermethode Zugriff auf Class haben, und Sie können konstruieren

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

und dann

List<Foo> list = mapper.readValue(new File("input.json"), type);
StaxMan
quelle
16
Es funktioniert: Ich habe Folgendes getan: JavaType topMost = mapper.getTypeFactory (). ConstructParametricType (MyWrapper.class, ActualClassRuntime.class); und dann hat der readValue und es hat endlich funktioniert :)
techzen
Ja, das funktioniert - danke, dass Sie auf die Methode zum Erstellen eines anderen generischen Typs als des Map / Collection-Typs hingewiesen haben!
StaxMan
1
@StaxMan wäre es besser, ClassMate ab sofort für solche Dinge zu verwenden?
Husayt
2
@husayt ja, technisch gesehen ist Java-Classmate Lib überlegen. Die Integration in Jackson ist jedoch etwas schwierig, nur weil Jacksons eigene Typabstraktion Teil der API ist. Auf lange Sicht wäre es großartig, herauszufinden, wie Jackson Klassenkameradencode verwenden kann, entweder eingebettet oder über dep.
StaxMan
1
Ich denke, Jackson sollte nicht in der Lage sein, Lücken in Generika zu schließen, aber so oder so macht es das sehr gut.
Adrian Baker
6

'JavaType' funktioniert !! Ich habe versucht, eine Liste in json String in ArrayList-Java-Objekte zu entfernen (deserialisieren) und hatte seit Tagen Probleme, eine Lösung zu finden.
Unten ist der Code, der mir endlich eine Lösung gegeben hat. Code:

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
quelle
Wie initialisiere ich TargetClass?
AZ_
Bitte zeigen Sie mir ein kleines Beispiel. Ich übergebe das Ziel der Klasse <?> Und erhalte dann target.getClassName ().
AZ_
1
Fügen Sie einen Konstruktor wie folgt hinzu: JsonMarshallerUnmarshaller <T> {private Class <T> targetClass; JsonMarshallerUnmarshaller (Klasse <T> c) {targetClass = c; }} Nehmen Sie jetzt entsprechende Änderungen an der Funktion 'Unmarschall' vor, um diese Klasse zu verwenden, anstatt überall getClass auszuführen.
Rushidesai1
Einige Anmerkungen: Code kann erheblich vereinfacht werden, indem festgestellt wird, dass alle Ausnahmen Subtypen von sind IOException(nur einen Fang benötigen) und dass der Standard-Introspektor für Anmerkungen bereits vorhanden ist. Sie JacksonAnnotationIntrospectormüssen also nichts tun ObjectMapper, konstruieren ihn einfach und es funktioniert.
StaxMan
Also diesen Code kann ich nicht einmal kompilieren. Haben Sie stattdessen ein Live-Beispiel zum Einfügen?
Schraubenschlüssel
0

Ich habe die Antwort von Rushidesai1 geändert , um ein funktionierendes Beispiel aufzunehmen.

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

Ausgabe

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"
}]
Mr. Polywhirl
quelle