Serialisasi dan Deserialisasi Senarai dengan Gson

1. Pengenalan

Dalam tutorial ini, kita akan meneroka beberapa kes serialisasi dan deserialisasi lanjutan untuk List menggunakan perpustakaan Gson Google.

2. Senarai Objek

Salah satu kes penggunaan yang biasa adalah untuk membuat siri dan mendeserisasi senarai POJO.

Pertimbangkan kelas:

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 menyenaraikan Senarai :

@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 dapat kita lihat, penyirian agak mudah.

Walau bagaimanapun, deserialisasi sukar. 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, walaupun kita akan mendapat Senarai saiz dua, selepas deserialization, ia tidak akan menjadi List of MyClass . Oleh itu, baris # 6 melemparkan ClassCastException .

Gson dapat membuat siri koleksi objek sewenang-wenangnya tetapi tidak dapat membuat deserialisasi data tanpa maklumat tambahan. Ini kerana tidak ada cara bagi pengguna untuk menunjukkan jenis objek yang dihasilkan. Sebaliknya, semasa melakukan deserialisasi, Koleksi mestilah jenis generik tertentu.

Cara yang betul untuk menghilangkan senarai ini 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 jenis yang betul untuk deserialized - ArrayList . Idiom yang digunakan untuk mendapatkan listOfMyClassObject sebenarnya menentukan kelas dalaman tempatan tanpa nama yang mengandungi kaedah getType () yang mengembalikan jenis parameter sepenuhnya.

3. Senarai Objek Polimorfik

3.1. Masalah

Pertimbangkan contoh hierarki haiwan:

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

Bagaimanakah kita membuat siri dan mendeserisasi Senarai ? Kita boleh menggunakan TypeToken seperti yang kita gunakan di bahagian sebelumnya. Walau bagaimanapun, Gson masih tidak dapat mengetahui jenis data konkrit dari objek yang tersimpan dalam senarai.

3.2. Menggunakan Custom Deserializer

Salah satu cara untuk menyelesaikannya adalah dengan menambahkan maklumat jenis ke JSON bersiri. Kami menghormati maklumat jenis itu semasa deserialisasi JSON. Untuk ini, kita perlu menulis serializer dan deserializer khas kita sendiri.

Pertama, kami akan memperkenalkan bidang String baru yang disebut jenis dalam kelas haiwan . Ia menyimpan nama ringkas kelas yang menjadi miliknya.

Mari 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 berjalan 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 mendeserisasi senarai, kami harus menyediakan deserializer tersuai:

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 mengekalkan pemetaan antara nama kelas dan jenis kelas.

Semasa deserialisasi, kami mula-mula mengeluarkan bidang jenis yang baru ditambahkan . Dengan menggunakan nilai ini, kami mencari di peta animalTypeRegistry untuk mendapatkan jenis data konkrit. Jenis data ini kemudian dihantar ke dariJson () .

Mari lihat cara menggunakan deserializer khas 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 terdapat dalam kod sumber Gson. Namun, ia tidak didedahkan oleh perpustakaan untuk digunakan oleh pengguna . Oleh itu, kita perlu membuat salinan kelas dalam projek Java kita.

Setelah ini selesai, kita dapat menggunakannya untuk mendeserisasi senarai kami:

@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 bahawa mekanisme yang mendasari masih sama.

Kami masih perlu memperkenalkan jenis maklumat semasa bersiri. Maklumat jenis kemudian boleh digunakan semasa deserialisasi. Oleh itu, jenis lapangan masih diperlukan di setiap kelas agar penyelesaian ini berjaya. Kita tidak perlu menulis deserializer sendiri.

RuntimeTypeAdapterFactory menyediakan penyesuai jenis yang betul berdasarkan nama bidang yang diberikan kepadanya dan subjenis yang didaftarkan.

4. Kesimpulan

Dalam artikel ini, kami melihat bagaimana untuk membuat siri dan mendeserisasi senarai objek menggunakan Gson.

Seperti biasa, kodnya tersedia di GitHub.