Jackson dan referensi tipe generik

107

Saya ingin menggunakan pustaka jackson json untuk metode umum sebagai berikut:

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

Sekarang masalahnya adalah ketika saya memanggil getMyObject () yang ada di dalam objek permintaan, jackson mengembalikan objek kustom bersarang sebagai LinkedHashMap. Apakah ada cara di mana saya menentukan bahwa objek T perlu dikembalikan ?. Contoh: Jika saya mengirimkan objek bertipe Customer maka Customer harus dikembalikan dari List tersebut ?.

Terima kasih.

techzen
sumber
Silakan tambahkan implementasi getT ()
Jim Garrison
Pertanyaan ini mirip dengan stackoverflow.com/questions/6062011/… tetapi mereka menyarankan untuk menentukan jenis menggunakan TypeFactory. Namun saya tidak tahu jenisnya pada waktu kompilasi ...
techzen
TypeFactory memiliki metode yang tidak membutuhkan kelas statis; createCollectionType dan sebagainya.
StaxMan
Silakan bagikan kode lengkap. Saya juga menghadapi masalah yang sama.
AZ_
Bukankah TypeReferenceabstrak?
Kyle Delaney

Jawaban:

195

Ini adalah masalah terkenal dengan penghapusan tipe Java: T hanyalah variabel tipe, dan Anda harus menunjukkan kelas sebenarnya, biasanya sebagai argumen Kelas. Tanpa informasi seperti itu, yang terbaik yang bisa dilakukan adalah dengan menggunakan batasan; dan T polos kira-kira sama dengan 'T memanjang Objek'. Dan Jackson kemudian akan mengikat Objek JSON sebagai Peta.

Dalam kasus ini, metode penguji perlu memiliki akses ke Kelas, dan Anda dapat membuat

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

lalu

List<Foo> list = mapper.readValue(new File("input.json"), type);
StaxMan
sumber
16
Ia bekerja: Saya melakukan hal berikut: JavaType topMost = mapper.getTypeFactory (). ConstructParametricType (MyWrapper.class, ActualClassRuntime.class); dan kemudian melakukan readValue dan akhirnya berhasil :)
techzen
Ya, itu berhasil - terima kasih telah menunjukkan metode untuk membuat tipe generik selain tipe Peta / Koleksi!
StaxMan
1
@StaxMan, apakah lebih baik menggunakan ClassMate untuk hal-hal semacam ini mulai sekarang?
husayt
2
@husayt ya, secara teknis java-classmate lib lebih unggul. Tetapi mengintegrasikannya dengan Jackson agak rumit hanya karena abstraksi tipe Jackson sendiri merupakan bagian terintegrasi dari API. Untuk jangka panjang, akan sangat bagus untuk mencari cara yang tepat untuk membuat Jackson menggunakan kode teman sekelas, baik yang disematkan atau melalui dep.
StaxMan
1
Saya merasa Jackson seharusnya tidak harus menutupi apa yang terasa seperti celah dalam obat generik, tetapi bagaimanapun juga, itu melakukannya dengan sangat baik.
Adrian Baker
6

'JavaType' bekerja !! Saya mencoba unmarshall (deserialize) Daftar di json String ke Objek java ArrayList dan berjuang untuk menemukan solusi sejak hari.
Di bawah ini adalah kode yang akhirnya memberi saya solusi. Kode:

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
sumber
Bagaimana cara menginisialisasi TargetClass?
AZ_
Tolong tunjukkan saya contoh kecil. Saya melewati Class <?> Target dan kemudian mendapatkan target.getClassName ().
AZ_
1
Tambahkan konstruktor sebagai berikut: JsonMarshallerUnmarshaller <T> {private Class <T> targetClass; JsonMarshallerUnmarshaller (Kelas <T> c) {targetClass = c; }} Buat perubahan yang sesuai sekarang ke fungsi 'unmarshal' untuk menggunakan kelas ini daripada melakukan getClass di mana-mana.
rushidesai1
Beberapa catatan: kode dapat banyak disederhanakan dengan mencatat bahwa semua pengecualian adalah subtipe dari IOException(hanya perlu satu tangkapan), dan bahwa introspektor anotasi default sudah ada JacksonAnnotationIntrospector- jadi tidak perlu melakukan apa pun ObjectMapper, cukup buat dan berfungsi.
StaxMan
Jadi kode ini bahkan tidak bisa saya kompilasi. Punya contoh langsung untuk ditempel?
Kunci pas
0

Saya memodifikasi jawaban rushidesai1 untuk memasukkan contoh yang berfungsi.

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

Keluaran

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"
}]
Tuan Polywhirl
sumber