Jackson i odniesienie do typów ogólnych

107

Chcę użyć biblioteki JSON JACKSON dla metody ogólnej w następujący sposób:

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

Teraz problem polega na tym, że kiedy wywołuję getMyObject (), który znajduje się wewnątrz obiektu żądania, jackson zwraca zagnieżdżony obiekt niestandardowy jako LinkedHashMap. Czy istnieje sposób, w jaki określę, że obiekt T ma zostać zwrócony ?. Na przykład: jeśli wysłałem obiekt typu Klient, to Klient powinien zostać zwrócony z tej Listy ?.

Dzięki.

techzen
źródło
Dodaj implementację getT ()
Jim Garrison,
To pytanie jest podobne do stackoverflow.com/questions/6062011/…, ale zasugerowali określenie typu przy użyciu TypeFactory. Jednak nie znam tego typu w czasie kompilacji ...
techzen
TypeFactory ma metody, które nie wymagają klasy statycznej; createCollectionType i tak dalej.
StaxMan
Udostępnij pełny kod. Ja też mam ten sam problem.
AZ_
Czy nie jest TypeReferenceabstrakcyjne?
Kyle Delaney

Odpowiedzi:

195

Jest to dobrze znany problem z wymazywaniem typu Java: T jest tylko zmienną typu i musisz wskazać aktualną klasę, zwykle jako argument klasy. Bez takich informacji najlepsze, co można zrobić, to użyć granic; a zwykłe T jest mniej więcej tym samym, co „T rozszerza obiekt”. Następnie Jackson powiąże obiekty JSON jako mapy.

W takim przypadku metoda testera musi mieć dostęp do Class, a Ty możesz konstruować

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

i wtedy

List<Foo> list = mapper.readValue(new File("input.json"), type);
StaxMan
źródło
16
Działa: wykonałem następujące czynności: JavaType topMost = mapper.getTypeFactory (). ConstructParametricType (MyWrapper.class, ActualClassRuntime.class); a potem wykonałem readValue i wreszcie zadziałało :)
techzen
Tak, to działa - dziękujemy za wskazanie metody tworzenia typu ogólnego innego niż typ mapy / kolekcji!
StaxMan
1
@StaxMan Czy od teraz lepiej byłoby używać ClassMate do tego typu rzeczy?
husayt
2
@husayt tak, technicznie java-classmate lib jest lepszy. Ale zintegrowanie go z Jacksonem jest trochę trudne tylko dlatego, że własna abstrakcja typu Jacksona jest zintegrowaną częścią API. Na dłuższą metę byłoby wspaniale wymyślić właściwy sposób na to, aby Jackson używał kodu kolegi z klasy, osadzonego lub przez dep.
StaxMan
1
Czuję, że Jackson nie powinien być zmuszony do zakrywania czegoś, co wydaje się być lukami w produktach generycznych, ale tak czy inaczej, robi to bardzo dobrze.
Adrian Baker,
6

„JavaType” działa !! Próbowałem usunąć (deserializować) Listę w JSON String do ArrayList java Objects i od wielu dni starałem się znaleźć rozwiązanie.
Poniżej znajduje się kod, który ostatecznie dał mi rozwiązanie. Kod:

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
źródło
Jak zainicjować TargetClass?
AZ_
Pokaż mi mały przykład. Przekazuję cel klasy <?>, A następnie otrzymuję target.getClassName ().
AZ_
1
Dodaj konstruktora w następujący sposób: JsonMarshallerUnmarshaller <T> {private Class <T> targetClass; JsonMarshallerUnmarshaller (Klasa <T> c) {targetClass = c; }} Dokonaj odpowiednich zmian w funkcji 'unmarshal', aby używać tej klasy zamiast wykonywania getClass wszędzie.
rushidesai1
Kilka uwag: kod można znacznie uprościć, zauważając, że wszystkie wyjątki są podtypami IOException(wystarczy jeden haczyk) i że domyślny introspektor adnotacji już jest JacksonAnnotationIntrospector- więc nie musisz nic robić ObjectMapper, po prostu go skonstruuj i działa.
StaxMan
Więc tego kodu nie mogę nawet skompilować. Masz zamiast tego przykład na żywo do wklejenia?
Klucz
0

Zmodyfikowałem odpowiedź rushidesai1, dodając działający przykład.

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

Wynik

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"
}]
Panie Polywhirl
źródło