Jackson et référence de type générique

107

Je souhaite utiliser la bibliothèque jackson json pour une méthode générique comme suit:

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

Maintenant, le problème est que lorsque j'appelle getMyObject () qui se trouve à l'intérieur de l'objet de requête, jackson renvoie l'objet personnalisé imbriqué en tant que LinkedHashMap. Existe-t-il un moyen de spécifier que l'objet T doit être retourné?. Par exemple: si j'ai envoyé un objet de type Client, alors le Client doit être renvoyé de cette Liste ?.

Merci.

techzen
la source
Veuillez ajouter l'implémentation de getT ()
Jim Garrison
Cette question est similaire à stackoverflow.com/questions/6062011/ ... mais ils ont suggéré de spécifier le type à l'aide de TypeFactory. Cependant, je ne connais pas le type au moment de la compilation ...
Techzen
TypeFactory a des méthodes qui n'ont pas besoin de classe statique; createCollectionType et ainsi de suite.
StaxMan
Veuillez partager le code complet. Je suis également confronté au même problème.
AZ_
N'est-ce pas TypeReferenceabstrait?
Kyle Delaney le

Réponses:

195

C'est un problème bien connu avec l'effacement de type Java: T est juste une variable de type, et vous devez indiquer la classe réelle, généralement comme argument de classe. Sans ces informations, le mieux que l'on puisse faire est d'utiliser des limites; et T simple est à peu près identique à «T étend l'objet». Et Jackson liera ensuite les objets JSON en tant que cartes.

Dans ce cas, la méthode du testeur doit avoir accès à la classe et vous pouvez construire

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

puis

List<Foo> list = mapper.readValue(new File("input.json"), type);
StaxMan
la source
16
Cela fonctionne: J'ai fait ce qui suit: JavaType topMost = mapper.getTypeFactory (). ConstructParametricType (MyWrapper.class, ActualClassRuntime.class); puis a fait le readValue et cela a finalement fonctionné :)
techzen
Oui, cela fonctionne - merci d'avoir indiqué la méthode de création de type générique autre que le type Map / Collection!
StaxMan
1
@StaxMan serait-il préférable d'utiliser ClassMate pour ce genre de choses à partir de maintenant?
husayt
2
@husayt oui, techniquement, java-classmate lib est supérieur. Mais l'intégrer à Jackson est un peu délicat simplement parce que l'abstraction de type de Jackson fait partie intégrante de l'API. Pour le long terme, ce serait formidable de trouver un moyen approprié de faire en sorte que Jackson utilise le code de camarade de classe, intégré ou via dep.
StaxMan
1
Je pense que Jackson ne devrait pas avoir à couvrir ce qui ressemble à des lacunes dans les génériques, mais de toute façon, il le fait très bien.
Adrian Baker
6

«JavaType» fonctionne !! J'essayais de démarshall (désérialiser) une liste dans json String en ArrayList java Objects et j'avais du mal à trouver une solution depuis des jours.
Voici le code qui m'a finalement apporté une solution. 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
la source
Comment initialiser TargetClass?
AZ_
Veuillez me montrer un petit exemple. Je passe la cible de classe <?>, Puis j'obtiens target.getClassName ().
AZ_
1
Ajoutez un constructeur comme suit: JsonMarshallerUnmarshaller <T> {private Class <T> targetClass; JsonMarshallerUnmarshaller (Classe <T> c) {targetClass = c; }} Apportez maintenant les modifications appropriées à la fonction 'unmarshal' pour utiliser cette classe au lieu de faire getClass partout.
rushidesai1
Quelques remarques: le code peut être beaucoup simplifié en notant que toutes les exceptions sont des sous-types de IOException(nécessite juste une capture), et que l'introspecteur d'annotation par défaut est déjà JacksonAnnotationIntrospector- donc pas besoin de faire quoi que ce soit ObjectMapper, il suffit de le construire et cela fonctionne.
StaxMan
Donc, ce code, je n'arrive même pas à le compiler. Vous avez un exemple en direct à coller à la place?
Clé du
0

J'ai modifié la réponse de rushidesai1 pour inclure un exemple fonctionnel.

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

Production

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"
}]
M. Polywhirl
la source