Jackson e riferimenti di tipo generico


107

Voglio usare la libreria jackson json per un metodo generico come segue:

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

Ora il problema è che quando chiamo getMyObject () che si trova all'interno dell'oggetto richiesta, jackson restituisce l'oggetto personalizzato annidato come LinkedHashMap. C'è un modo in cui specifichi che l'oggetto T deve essere restituito ?. Ad esempio: se ho inviato un oggetto di tipo Cliente, il cliente dovrebbe essere restituito da quella lista ?.

Grazie.


Si prega di aggiungere l'implementazione di getT ()
Jim Garrison

Questa domanda è simile a stackoverflow.com/questions/6062011/… ma hanno suggerito di specificare il tipo utilizzando TypeFactory. Tuttavia non conosco il tipo in fase di compilazione ...
techzen

TypeFactory ha metodi che non richiedono una classe statica; createCollectionType e così via.
StaxMan

Si prega di condividere il codice completo. Sto anche affrontando lo stesso problema.
AZ_

Non è TypeReferenceastratto?
Kyle Delaney,

Risposte:


194

Questo è un problema ben noto con la cancellazione del tipo Java: T è solo una variabile di tipo e devi indicare la classe effettiva, di solito come argomento Classe. Senza tali informazioni, la cosa migliore che si può fare è usare i limiti; e la T semplice è più o meno la stessa di "T estende l'oggetto". E Jackson legherà quindi gli oggetti JSON come mappe.

In questo caso, il metodo tester deve avere accesso a Class e tu puoi costruire

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

e poi

List<Foo> list = mapper.readValue(new File("input.json"), type);

16
Funziona: ho fatto quanto segue: JavaType topMost = mapper.getTypeFactory (). ConstructParametricType (MyWrapper.class, ActualClassRuntime.class); e poi ha fatto il readValue e finalmente ha funzionato :)
techzen

Sì, funziona, grazie per aver indicato il metodo per creare un tipo generico diverso dal tipo di mappa / raccolta!
StaxMan

1
@StaxMan sarebbe meglio usare ClassMate per questo genere di cose da ora?
husayt

2
@husayt sì, tecnicamente java-classmate lib è superiore. Ma integrarlo con Jackson è un po 'complicato solo perché l'astrazione dei tipi di Jackson è parte integrante dell'API. A lungo termine sarebbe fantastico trovare un modo corretto per far usare a Jackson il codice dei compagni di classe, incorporato o tramite dep.
StaxMan

1
Sento che Jackson non dovrebbe dover coprire quelle che sembrano delle lacune nei generici, ma in ogni caso, lo fa molto bene.
Adrian Baker

6

"JavaType" funziona !! Stavo cercando di unmarshall (deserializzare) un elenco in json String in ArrayList java Objects e stavo lottando per trovare una soluzione da giorni.
Di seguito è riportato il codice che finalmente mi ha dato la soluzione. Codice:

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

Come inizializzare TargetClass?
AZ_

Per favore mostrami un piccolo esempio. Sto passando Class <?> Target e quindi ottengo target.getClassName ().
AZ_

1
Aggiungere un costruttore come segue: JsonMarshallerUnmarshaller <T> {private Class <T> targetClass; JsonMarshallerUnmarshaller (Classe <T> c) {targetClass = c; }} Apporta ora le modifiche appropriate alla funzione 'unmarshal' per usare questa classe invece di eseguire getClass ovunque.
rushidesai1

Un paio di note: il codice può essere semplificato molto notando che tutte le eccezioni sono sottotipi di IOException(richiede solo una cattura) e che l'introspettore di annotazioni predefinito è già JacksonAnnotationIntrospector- quindi non c'è bisogno di fare nulla per farlo ObjectMapper, basta costruirlo e funziona.
StaxMan

Quindi questo codice non riesco nemmeno a compilare. Hai qualche esempio dal vivo da incollare invece?
Chiave inglese

0

Ho modificato la risposta di rushidesai1 per includere un esempio funzionante.

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

Produzione

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