Serialisasi dan Deserialisasi Daftar dengan Gson

1. Perkenalan

Dalam tutorial ini, kita akan menjelajahi beberapa kasus serialisasi dan deserialisasi lanjutan untuk Daftar menggunakan pustaka Google Gson.

2. Daftar Objek

Salah satu kasus penggunaan yang umum adalah membuat serial dan deserialisasi daftar POJO.

Pertimbangkan kelasnya:

public class MyClass { private int id; private String name; public MyClass(int id, String name) { this.id = id; this.name = name; } // getters and setters }

Inilah cara kami membuat daftar serial :

@Test public void givenListOfMyClass_whenSerializing_thenCorrect() { List list = Arrays.asList(new MyClass(1, "name1"), new MyClass(2, "name2")); Gson gson = new Gson(); String jsonString = gson.toJson(list); String expectedString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]"; assertEquals(expectedString, jsonString); }

Seperti yang bisa kita lihat, serialisasi cukup mudah.

Namun, deserialisasi itu rumit. Inilah cara yang salah untuk melakukannya:

@Test(expected = ClassCastException.class) public void givenJsonString_whenIncorrectDeserializing_thenThrowClassCastException() { String inputString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]"; Gson gson = new Gson(); List outputList = gson.fromJson(inputString, ArrayList.class); assertEquals(1, outputList.get(0).getId()); }

Di sini, meskipun kami akan mendapatkan Daftar ukuran dua, pasca-deserialization, itu tidak akan menjadi Daftar dari MyClass . Oleh karena itu, baris # 6 menampilkan ClassCastException .

Gson dapat membuat serial kumpulan objek arbitrer tetapi tidak dapat melakukan deserialisasi data tanpa informasi tambahan. Itu karena tidak ada cara bagi pengguna untuk menunjukkan tipe objek yang dihasilkan. Sebaliknya, saat melakukan deserialisasi, Koleksi harus dari jenis umum yang spesifik.

Cara yang benar untuk menghilangkan derialisasi Daftar adalah:

@Test public void givenJsonString_whenDeserializing_thenReturnListOfMyClass() { String inputString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]"; List inputList = Arrays.asList(new MyClass(1, "name1"), new MyClass(2, "name2")); Type listOfMyClassObject = new TypeToken
    
     () {}.getType(); Gson gson = new Gson(); List outputList = gson.fromJson(inputString, listOfMyClassObject); assertEquals(inputList, outputList); }
    

Di sini, kami menggunakan TypeToken Gson untuk menentukan tipe yang benar untuk dideserialisasi - ArrayList . Idiom yang digunakan untuk mendapatkan listOfMyClassObject sebenarnya mendefinisikan kelas dalam lokal anonim yang berisi metode getType () yang mengembalikan tipe berparameter penuh.

3. Daftar Objek Polimorfik

3.1. Masalah

Pertimbangkan contoh hierarki kelas hewan:

public abstract class Animal { // ... } public class Dog extends Animal { // ... } public class Cow extends Animal { // ... }

Bagaimana cara kami membuat serial dan deserialisasi Daftar ? Kita bisa menggunakan TypeToken seperti yang kami gunakan di bagian sebelumnya. Namun, Gson masih tidak dapat mengetahui tipe data konkret dari objek yang disimpan dalam daftar.

3.2. Menggunakan Deserializer Kustom

Salah satu cara untuk mengatasinya adalah dengan menambahkan informasi tipe ke JSON serial. Kami menghormati informasi jenis itu selama deserialisasi JSON. Untuk ini, kita perlu membuat serializer dan deserializer kustom kita sendiri.

Pertama, kami akan memperkenalkan bidang String baru yang disebut tipe di kelas dasar Animal . Ini menyimpan nama sederhana dari kelas yang memilikinya.

Mari kita lihat kelas sampel kami:

public abstract class Animal { public String type = "Animal"; }
public class Dog extends Animal { private String petName; public Dog() { petName = "Milo"; type = "Dog"; } // getters and setters }
public class Cow extends Animal { private String breed; public Cow() { breed = "Jersey"; type = "Cow"; } // getters and setters }

Serialisasi akan terus berfungsi seperti sebelumnya tanpa masalah:

@Test public void givenPolymorphicList_whenSerializeWithTypeAdapter_thenCorrect() { String expectedString = "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]"; List inList = new ArrayList(); inList.add(new Dog()); inList.add(new Cow()); String jsonString = new Gson().toJson(inList); assertEquals(expectedString, jsonString); }

Untuk membatalkan daftar, kami harus menyediakan deserializer khusus:

public class AnimalDeserializer implements JsonDeserializer { private String animalTypeElementName; private Gson gson; private Map
    
      animalTypeRegistry; public AnimalDeserializer(String animalTypeElementName) { this.animalTypeElementName = animalTypeElementName; this.gson = new Gson(); this.animalTypeRegistry = new HashMap(); } public void registerBarnType(String animalTypeName, Class animalType) { animalTypeRegistry.put(animalTypeName, animalType); } public Animal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { JsonObject animalObject = json.getAsJsonObject(); JsonElement animalTypeElement = animalObject.get(animalTypeElementName); Class animalType = animalTypeRegistry.get(animalTypeElement.getAsString()); return gson.fromJson(animalObject, animalType); } }
    

Di sini, peta animalTypeRegistry mempertahankan pemetaan antara nama kelas dan jenis kelas.

Selama deserialisasi, pertama-tama kami mengekstrak bidang tipe yang baru ditambahkan . Dengan menggunakan nilai ini, kami melakukan pencarian di peta animalTypeRegistry untuk mendapatkan tipe data konkret. Tipe data ini kemudian diteruskan ke fromJson () .

Mari kita lihat cara menggunakan deserializer khusus kami:

@Test public void givenPolymorphicList_whenDeserializeWithTypeAdapter_thenCorrect() { String inputString = "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]"; AnimalDeserializer deserializer = new AnimalDeserializer("type"); deserializer.registerBarnType("Dog", Dog.class); deserializer.registerBarnType("Cow", Cow.class); Gson gson = new GsonBuilder() .registerTypeAdapter(Animal.class, deserializer) .create(); List outList = gson.fromJson(inputString, new TypeToken
    
     (){}.getType()); assertEquals(2, outList.size()); assertTrue(outList.get(0) instanceof Dog); assertTrue(outList.get(1) instanceof Cow); }
    

3.3. Menggunakan RuntimeTypeAdapterFactory

Alternatif untuk menulis deserializer khusus adalah dengan menggunakan kelas RuntimeTypeAdapterFactory yang ada di kode sumber Gson. Namun, itu tidak diekspos oleh perpustakaan untuk digunakan pengguna . Karenanya, kita harus membuat salinan kelas dalam proyek Java kita.

Setelah ini selesai, kita dapat menggunakannya untuk menghapus daftar nama:

@Test public void givenPolymorphicList_whenDeserializeWithRuntimeTypeAdapter_thenCorrect() { String inputString = "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]"; Type listOfAnimals = new TypeToken
    
     (){}.getType(); RuntimeTypeAdapterFactory adapter = RuntimeTypeAdapterFactory.of(Animal.class, "type") .registerSubtype(Dog.class) .registerSubtype(Cow.class); Gson gson = new GsonBuilder().registerTypeAdapterFactory(adapter).create(); List outList = gson.fromJson(inputString, listOfAnimals); assertEquals(2, outList.size()); assertTrue(outList.get(0) instanceof Dog); assertTrue(outList.get(1) instanceof Cow); }
    

Perhatikan bahwa mekanisme dasarnya masih sama.

Kami masih perlu memperkenalkan informasi tipe selama serialisasi. Informasi jenis nantinya dapat digunakan selama deserialization. Oleh karena itu, jenis bidang masih diperlukan di setiap kelas agar solusi ini berfungsi. Kami hanya tidak perlu menulis deserializer kami sendiri.

RuntimeTypeAdapterFactory menyediakan adaptor tipe yang benar berdasarkan nama bidang yang diteruskan padanya dan subtipe terdaftar.

4. Kesimpulan

Di artikel ini, kami melihat cara membuat serial dan deserialisasi daftar objek menggunakan Gson.

Seperti biasa, kode tersedia di GitHub.