Jackson - Deserialize menggunakan kelas generik

Jawaban:

238

Anda perlu membuat TypeReferenceobjek untuk setiap jenis generik yang Anda gunakan dan menggunakannya untuk deserialization. Sebagai contoh -

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});
Eser Aygün
sumber
Saya harus menggunakannya sebagai TypeReference <Data <T>> () {} ... Tapi saya mendapatkan kesalahan berikut - tidak dapat mengakses private java.lang.class.Class () dari java.lang.class. Gagal mengatur akses. Tidak dapat membuat konstruktor java.lang.Class dapat diakses
gnjago
Tidak, tidak Data<T>, itu BUKAN tipe. Anda harus menentukan kelas aktual; selain itu sama dengan Data<Object>.
StaxMan
18
Bagaimana jika saya tidak tahu kelas apa itu sampai runtime? Saya akan mendapatkan kelas sebagai parameter saat runtime. Suka publik ini <T> batal deSerialize (Kelas <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, TypeReference baru <Json <T>> () {});}
gnjago
1
Saya telah mengajukan pertanyaan lengkap dengan benar di sini stackoverflow.com/questions/11659844/...
gnjago
apa nama paket lengkap TypeReference? itu com.fasterxml.jackson.core.type?
Lei Yang
88

Anda tidak dapat melakukan itu: Anda harus menentukan jenis yang diselesaikan sepenuhnya, seperti Data<MyType>. Thanyalah sebuah variabel, dan seperti tidak ada artinya.

Tetapi jika yang Anda maksud itu Takan diketahui, hanya saja tidak statis, Anda perlu membuat yang setara dengan TypeReferencedinamis. Pertanyaan lain yang dirujuk mungkin sudah menyebutkan ini, tetapi seharusnya terlihat seperti:

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}
StaxMan
sumber
2
Bagaimana jika saya tidak tahu kelas apa itu sampai runtime? Saya akan mendapatkan kelas sebagai parameter saat runtime. Suka publik ini <T> batal deSerialize (Kelas <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, TypeReference baru <Json <T>> () {});}
gnjago
1
Saya telah mengajukan pertanyaan lengkap di sini stackoverflow.com/questions/11659844/...
gnjago
2
Kemudian hanya lulus kelas apa adanya, tidak perlu untuk TypeReference: return mapper.readValue(json, clazz);Apa sebenarnya masalahnya di sini?
StaxMan
2
Masalahnya adalah bahwa "Data" adalah kelas generik. Saya perlu menentukan tipe T saat runtime. Parameter clazz adalah apa yang kita gunakan saat runtime. Jadi, bagaimana cara memanggil readValue? menyebutnya dengan TypeReference> Json <T>> yang baru tidak berfungsi. Pertanyaan lengkapnya ada di sini stackoverflow.com/questions/11659844/…
gnjago
2
Baik. Maka Anda perlu menggunakan TypeFactory.. Saya akan mengedit jawaban saya.
StaxMan
30

Hal pertama yang Anda lakukan adalah membuat cerita bersambung, maka Anda dapat melakukan deserialize.
jadi ketika Anda membuat cerita bersambung, Anda harus menggunakan @JsonTypeInfojackson untuk menulis informasi kelas ke dalam data json Anda. Yang bisa Anda lakukan adalah seperti ini:

Class Data <T> {
    int found;
    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    Class<T> hits
}

Kemudian ketika Anda deserialize, Anda akan menemukan jackson telah deserialize data Anda ke dalam sebuah kelas yang variabel Anda hit sebenarnya saat runtime.

CharlieQ
sumber
tidak berfungsi, mendapatkan kesalahan di bawah ini com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Id tipe hilang ketika mencoba menyelesaikan subtipe [tipe sederhana, kelas java.lang.Object]: properti type id yang hilang '@class' (untuk POJO) properti 'data')
gaurav9620
15

Untuk Data kelas <>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)
Devesh
sumber
Ini sekarang sudah usang.
Evan Gertis
13

Cukup tulis metode statis di kelas Util. Saya membaca Json dari file. Anda dapat memberikan String juga ke readValue

public static <T> T convertJsonToPOJO(String filePath, Class<?> target) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(new File(filePath), objectMapper .getTypeFactory().constructCollectionType(List.class, Class.forName(target.getName())));
}

Pemakaian:

List<TaskBean> list =  Util.<List<TaskBean>>convertJsonToPOJO("E:/J2eeWorkspaces/az_workspace_svn/az-client-service/dir1/dir2/filename.json", TaskBean.class);
AZ_
sumber
7

Anda bisa membungkusnya di kelas lain yang tahu tipe tipe generik Anda.

Misalnya,

class Wrapper {
 private Data<Something> data;
}
mapper.readValue(jsonString, Wrapper.class);

Here Something adalah tipe konkret. Anda memerlukan pembungkus per jenis yang telah diverifikasi. Kalau tidak, Jackson tidak tahu benda apa yang harus dibuat.

sksamuel
sumber
6

String JSON yang perlu deserialized harus berisi informasi tipe tentang parameter T.
Anda harus meletakkan anotasi Jackson pada setiap kelas yang dapat diteruskan sebagai parameter Tke kelas Datasehingga informasi tipe tentang tipe parameter Tdapat dibaca dari / ditulis ke string JSON oleh Jackson.

Mari kita asumsikan bahwa Tdapat berupa kelas apa saja yang memperluas kelas abstrak Result.

class Data <T extends Result> {
    int found;
    Class<T> hits
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ImageResult.class, name = "ImageResult"),
        @JsonSubTypes.Type(value = NewsResult.class, name = "NewsResult")})
public abstract class Result {

}

public class ImageResult extends Result {

}

public class NewsResult extends Result {

}

Setelah masing-masing kelas (atau supertipe umum mereka) yang dapat dilewati sebagai parameter T dianotasi, Jackson akan memasukkan informasi tentang parameter Tdi JSON. JSON tersebut kemudian dapat dideeralisasi tanpa mengetahui parameter Tpada waktu kompilasi. Tautan dokumentasi Jackson
ini berbicara tentang Deserialisasi Polimorfik tetapi juga bermanfaat untuk merujuk pada pertanyaan ini.

Sanjeev Sachdev
sumber
dan bagaimana cara mengelola ini jika saya ingin memiliki Daftar? Seperti katakanlah Daftar <ImageResult>
Luca Archidiacono
5

Dari Jackson 2.5, cara elegan untuk menyelesaikannya adalah menggunakan metode TypeFactory.constructParametricType (Class parametrized, Class ... parameterClasses) yang memungkinkan untuk mendefinisikan straigthly Jackson JavaTypedengan menentukan kelas parameter dan tipe parameternya.

Seandainya Anda ingin deserialize Data<String>, Anda dapat melakukan:

// the json variable may be a String, an InputStream and so for...
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class);
Data<String> data = mapper.readValue(json, type);

Perhatikan bahwa jika kelas mendeklarasikan beberapa tipe parameter, itu tidak akan menjadi lebih sulit:

class Data <T, U> {
    int found;
    Class<T> hits;
    List<U> list;
}

Kita bisa lakukan :

JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);
davidxxx
sumber
Luar biasa, terima kasih itu berhasil untuk saya. Dengan typereference saya mendapatkan pengecualian siaran dari peta ke objek tertentu tetapi ini benar-benar berhasil.
Tacsiazuma
1
public class Data<T> extends JsonDeserializer implements ContextualDeserializer {
    private Class<T> cls;
    public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty prop) throws JsonMappingException {
        cls = (Class<T>) ctx.getContextualType().getRawClass();
        return this;
    }
    ...
 }
mikka22
sumber
0

jika Anda menggunakan scala dan mengetahui tipe generik pada waktu kompilasi, tetapi tidak ingin meneruskan TypeReference secara manual di mana saja di semua server api Anda, Anda dapat menggunakan kode berikut (dengan jackson 2.9.5):

def read[T](entityStream: InputStream)(implicit typeTag: WeakTypeTag[T]): T = {

    //nathang: all of this *crazy* scala reflection allows us to handle List[Seq[Map[Int,Value]]]] without passing
    // new TypeReference[List[Seq[Map[Int,Value]]]]](){} to the function

    def recursiveFindGenericClasses(t: Type): JavaType = {
      val current = typeTag.mirror.runtimeClass(t)

      if (t.typeArgs.isEmpty) {
        val noSubtypes = Seq.empty[Class[_]]
        factory.constructParametricType(current, noSubtypes:_*)
      }

      else {
        val genericSubtypes: Seq[JavaType] = t.typeArgs.map(recursiveFindGenericClasses)
        factory.constructParametricType(current, genericSubtypes:_*)
      }

    }

    val javaType = recursiveFindGenericClasses(typeTag.tpe)

    json.readValue[T](entityStream, javaType)
  }

yang bisa digunakan seperti ini:

read[List[Map[Int, SomethingToSerialize]]](inputStream)
nathan g
sumber
0

Untuk menghapus deserialisasi string JSON generik ke objek Java dengan Jackson, Anda perlu:

  1. Untuk mendefinisikan kelas JSON.

  2. Lakukan pemetaan atribut.

Kode akhir, teruji, dan siap digunakan:

static class MyJSON {

    private Map<String, Object> content = new HashMap<>();

    @JsonAnySetter
    public void setContent(String key, Object value) {
        content.put(key, value);
    }
}

String json = "{\"City\":\"Prague\"}";

try {

    MyPOJO myPOJO = objectMapper.readValue(json, MyPOJO.class);

    String jsonAttVal = myPOJO.content.get("City").toString();

    System.out.println(jsonAttVal);

} catch (IOException e) {
    e.printStackTrace();
}

Penting:
@JsonAnySetter anotasi adalah wajib, itu memastikan JSON-parsing dan populasi umum.

Untuk kasus yang lebih rumit dengan array bersarang, lihat referensi Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object

Mike B.
sumber
0

Contoh keputusan yang tidak terlalu bagus, tetapi sederhana (tidak hanya untuk Jackson, juga untuk Spring RestTemplate, dll.):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Demel
sumber