Polimorfisme dengan gson

103

Saya punya masalah deserializing string json dengan Gson. Saya menerima serangkaian perintah. Perintah dapat dimulai, berhenti, beberapa jenis perintah lainnya. Tentu saya memiliki polimorfisme, dan perintah start / stop mewarisi dari perintah.

Bagaimana saya bisa membuat serial kembali ke objek perintah yang benar menggunakan gson?

Sepertinya saya hanya mendapatkan tipe dasar, yaitu tipe yang dideklarasikan dan tidak pernah tipe runtime.

Sophie
sumber

Jawaban:

120

Ini agak terlambat tetapi saya harus melakukan hal yang persis sama hari ini. Jadi, berdasarkan penelitian saya dan ketika menggunakan gson-2.0 Anda benar-benar tidak ingin menggunakan metode registerTypeHierarchyAdapter , melainkan registerTypeAdapter yang lebih biasa . Dan Anda tentu tidak perlu melakukan instanceofs atau menulis adaptor untuk kelas turunan: cukup satu adaptor untuk kelas atau antarmuka dasar, asalkan Anda puas dengan serialisasi default dari kelas turunan. Bagaimanapun, ini kodenya (paket dan impor dihapus) (juga tersedia di github ):

Kelas dasar (antarmuka dalam kasus saya):

public interface IAnimal { public String sound(); }

Dua kelas turunan, Cat:

public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}

Dan Anjing:

public class Dog implements IAnimal {

    public String name;
    public int ferocity;

    public Dog(String name, int ferocity) {
        super();
        this.name = name;
        this.ferocity = ferocity;
    }

    @Override
    public String sound() {
        return name + " : \"bark\" (ferocity level:" + ferocity + ")";
    }
}

IAnimalAdapter:

public class IAnimalAdapter implements JsonSerializer<IAnimal>, JsonDeserializer<IAnimal>{

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(IAnimal src, Type typeOfSrc,
            JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src); 
        retValue.add(INSTANCE, elem);
        return retValue;
    }

    @Override
    public IAnimal deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException  {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

Dan kelas Tes:

public class Test {

    public static void main(String[] args) {
        IAnimal animals[] = new IAnimal[]{new Cat("Kitty"), new Dog("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(IAnimal.class, new IAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, IAnimal.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            IAnimal animal2 = gsonExt.fromJson(animalJson, IAnimal.class);
            System.out.println(animal2.sound());
        }
    }
}

Saat Anda menjalankan Test :: main Anda mendapatkan output berikut:

serialized with the custom serializer:
{"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Cat","INSTANCE":{"name":"Kitty"}}
Kitty : "meaow"
serialized with the custom serializer:
{"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Dog","INSTANCE":{"name":"Brutus","ferocity":5}}
Brutus : "bark" (ferocity level:5)

Saya sebenarnya telah melakukan hal di atas menggunakan metode registerTypeHierarchyAdapter juga, tetapi itu tampaknya memerlukan penerapan kelas serializer / deserializer DogAdapter dan CatAdapter khusus yang sulit dipertahankan kapan pun Anda ingin menambahkan bidang lain ke Dog atau ke Cat.

Marcus Junius Brutus
sumber
5
Perhatikan bahwa serialisasi nama kelas dan deserialisasi (dari input pengguna) menggunakan Class.forName dapat memberikan implikasi keamanan dalam beberapa situasi, dan oleh karena itu tidak disarankan oleh tim pengembang Gson. code.google.com/p/google-gson/issues/detail?id=340#c2
Programmer Bruce
4
Bagaimana Anda bisa mengatur untuk tidak mendapatkan loop tak terbatas dalam serialisasi, Anda memanggil context.serialize (src); yang akan memanggil adaptor Anda lagi. Inilah yang terjadi di kode serupa saya.
che javara
6
Salah. Solusi ini tidak berhasil. Jika Anda memanggil context.serialize dengan cara apa pun, Anda akan berakhir dengan rekursi tak terbatas. Saya bertanya-tanya mengapa orang memposting tanpa benar-benar menguji kode. Saya mencoba dengan 2.2.1. Lihat bug yang dijelaskan di stackoverflow.com/questions/13244769/…
che javara
4
@MarcusJuniusBrutus Saya menjalankan kode Anda dan tampaknya itu hanya berfungsi dalam kasus khusus ini - karena Anda telah menentukan IAnimal superinterface, dan IAnimalAdapter menggunakannya. Jika Anda malah hanya memiliki 'Cat' maka Anda akan mendapatkan masalah rekursi tak terbatas. Jadi solusi ini masih tidak berfungsi dalam kasus umum - hanya jika Anda dapat menentukan antarmuka umum. Dalam kasus saya tidak ada antarmuka jadi saya harus menggunakan pendekatan yang berbeda dengan TypeAdapterFactory.
che javara
2
Pengguna src.getClass (). GetName () bukan src.getClass (). GetCanonicalName (). Ini meads kode akan bekerja untuk kelas dalam / bersarang juga.
mR_fr0g
13

Gson saat ini memiliki mekanisme untuk mendaftarkan Adaptor Hierarki Tipe yang dilaporkan dapat dikonfigurasi untuk deserialisasi polimorfik sederhana, tetapi saya tidak melihat bagaimana masalahnya, karena Adaptor Hierarki Tipe tampaknya hanya merupakan pembuat serializer / deserializer / instance gabungan, menyerahkan detail pembuatan instance kepada pembuat kode, tanpa memberikan pendaftaran jenis polimorfik yang sebenarnya.

Sepertinya Gson akan segera memiliki RuntimeTypeAdapterdeserialisasi polimorfik yang lebih sederhana. Lihat http://code.google.com/p/google-gson/issues/detail?id=231 untuk info lebih lanjut.

Jika penggunaan yang baru RuntimeTypeAdaptertidak memungkinkan, dan Anda harus menggunakan Gson, maka saya pikir Anda harus menggulirkan solusi Anda sendiri, mendaftarkan deserializer khusus baik sebagai Adaptor Hierarki Tipe atau sebagai Adaptor Tipe. Berikut adalah salah satu contohnya.

// output:
//     Starting machine1
//     Stopping machine2

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

public class Foo
{
  // [{"machine_name":"machine1","command":"start"},{"machine_name":"machine2","command":"stop"}]
  static String jsonInput = "[{\"machine_name\":\"machine1\",\"command\":\"start\"},{\"machine_name\":\"machine2\",\"command\":\"stop\"}]";

  public static void main(String[] args)
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    CommandDeserializer deserializer = new CommandDeserializer("command");
    deserializer.registerCommand("start", Start.class);
    deserializer.registerCommand("stop", Stop.class);
    gsonBuilder.registerTypeAdapter(Command.class, deserializer);
    Gson gson = gsonBuilder.create();
    Command[] commands = gson.fromJson(jsonInput, Command[].class);
    for (Command command : commands)
    {
      command.execute();
    }
  }
}

class CommandDeserializer implements JsonDeserializer<Command>
{
  String commandElementName;
  Gson gson;
  Map<String, Class<? extends Command>> commandRegistry;

  CommandDeserializer(String commandElementName)
  {
    this.commandElementName = commandElementName;
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    gson = gsonBuilder.create();
    commandRegistry = new HashMap<String, Class<? extends Command>>();
  }

  void registerCommand(String command, Class<? extends Command> commandInstanceClass)
  {
    commandRegistry.put(command, commandInstanceClass);
  }

  @Override
  public Command deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException
  {
    try
    {
      JsonObject commandObject = json.getAsJsonObject();
      JsonElement commandTypeElement = commandObject.get(commandElementName);
      Class<? extends Command> commandInstanceClass = commandRegistry.get(commandTypeElement.getAsString());
      Command command = gson.fromJson(json, commandInstanceClass);
      return command;
    }
    catch (Exception e)
    {
      throw new RuntimeException(e);
    }
  }
}

abstract class Command
{
  String machineName;

  Command(String machineName)
  {
    this.machineName = machineName;
  }

  abstract void execute();
}

class Stop extends Command
{
  Stop(String machineName)
  {
    super(machineName);
  }

  void execute()
  {
    System.out.println("Stopping " + machineName);
  }
}

class Start extends Command
{
  Start(String machineName)
  {
    super(machineName);
  }

  void execute()
  {
    System.out.println("Starting " + machineName);
  }
}
Programmer Bruce
sumber
Jika Anda dapat mengubah API, perhatikan bahwa Jackson saat ini memiliki mekanisme deserialisasi polimorfik yang relatif sederhana. Saya memposting beberapa contoh di programmerbruce.blogspot.com/2011/05/…
Programmer Bruce
RuntimeTypeAdaptersudah selesai, sayangnya sepertinya belum ada di inti Gson. :-(
Jonathan
8

Marcus Junius Brutus memiliki jawaban yang bagus (terima kasih!). Untuk memperluas contohnya, Anda dapat membuat kelas adaptornya generik untuk bekerja untuk semua jenis objek (Bukan hanya IAnimal) dengan perubahan berikut:

class InheritanceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T>
{
....
    public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context)
....
    public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
....
}

Dan di Kelas Tes:

public class Test {
    public static void main(String[] args) {
        ....
            builder.registerTypeAdapter(IAnimal.class, new InheritanceAdapter<IAnimal>());
        ....
}
pengguna2242263
sumber
1
Setelah menerapkan solusinya, pikiran saya berikutnya adalah melakukan persis seperti ini :-)
David Levy
7

GSON memiliki kasus uji yang cukup bagus di sini yang menunjukkan cara menentukan dan mendaftarkan adaptor hierarki tipe.

http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739

Untuk menggunakannya lakukan ini:

    gson = new GsonBuilder()
          .registerTypeAdapter(BaseQuestion.class, new BaseQuestionAdaptor())
          .create();

Metode serialisasi adaptor bisa menjadi pemeriksaan berjenjang jika-lain dari jenis apa itu serialisasi.

    JsonElement result = new JsonObject();

    if (src instanceof SliderQuestion) {
        result = context.serialize(src, SliderQuestion.class);
    }
    else if (src instanceof TextQuestion) {
        result = context.serialize(src, TextQuestion.class);
    }
    else if (src instanceof ChoiceQuestion) {
        result = context.serialize(src, ChoiceQuestion.class);
    }

    return result;

Deserializing agak sedikit hacky. Dalam contoh unit test, ia memeriksa keberadaan atribut tell-tale untuk memutuskan kelas mana yang akan dideserialisasi. Jika Anda dapat mengubah sumber objek yang Anda serialisasi, Anda dapat menambahkan atribut 'classType' ke setiap instance yang menyimpan FQN dari nama kelas instance. Ini sangat tidak berorientasi objek.

kc sham
sumber
4

Google telah merilis RuntimeTypeAdapterFactory sendiri untuk menangani polimorfisme tetapi sayangnya ini bukan bagian dari inti gson (Anda harus menyalin dan menempel kelas di dalam proyek Anda).

Contoh:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

Di sini saya telah memposting contoh kerja lengkapnya menggunakan model Hewan, Anjing dan Kucing.

Saya pikir lebih baik mengandalkan adaptor ini daripada menerapkannya kembali dari awal.

db80.dll
sumber
2

Waktu telah lama berlalu, tetapi saya tidak dapat menemukan solusi yang benar-benar bagus secara online .. Berikut ini sedikit perubahan pada solusi @ MarcusJuniusBrutus, yang menghindari rekursi tak terbatas.

Pertahankan deserializer yang sama, tetapi lepaskan serializer -

public class IAnimalAdapter implements JsonDeSerializer<IAnimal> {
  private static final String CLASSNAME = "CLASSNAME";
  private static final String INSTANCE  = "INSTANCE";

  @Override
  public IAnimal deserialize(JsonElement json, Type typeOfT,
        JsonDeserializationContext context) throws JsonParseException  {
    JsonObject jsonObject =  json.getAsJsonObject();
    JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
    String className = prim.getAsString();

    Class<?> klass = null;
    try {
        klass = Class.forName(className);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        throw new JsonParseException(e.getMessage());
    }
    return context.deserialize(jsonObject.get(INSTANCE), klass);
  }
}

Kemudian, di kelas asli Anda, tambahkan bidang dengan @SerializedName("CLASSNAME"). Triknya sekarang adalah menginisialisasi ini di konstruktor kelas dasar , jadi buat antarmuka Anda menjadi kelas abstrak.

public abstract class IAnimal {
  @SerializedName("CLASSNAME")
  public String className;

  public IAnimal(...) {
    ...
    className = this.getClass().getName();
  }
}

Alasan tidak ada rekursi tak terbatas di sini adalah karena kita meneruskan kelas runtime aktual (yaitu, Dog bukan IAnimal) ke context.deserialize. Ini tidak akan memanggil adaptor tipe kami, selama kami menggunakan registerTypeAdapterdan tidakregisterTypeHierarchyAdapter

Ginandi
sumber
2

Jawaban yang Diperbarui - Bagian terbaik dari semua jawaban lainnya

Saya menjelaskan solusi untuk berbagai kasus penggunaan dan akan menangani masalah rekursi tak terbatas juga

  • Kasus 1: Anda mengendalikan kelas , yaitu, Anda bisa menulis kelas Anda sendiri Cat, Dogserta kelasIAnimal antarmuka. Anda cukup mengikuti solusi yang disediakan oleh @ marcus-junius-brutus (jawaban teratas)

    Tidak akan ada rekursi tak terbatas jika ada antarmuka dasar yang sama seperti IAnimal

    Tetapi, bagaimana jika saya tidak ingin mengimplementasikan IAnimalatau antarmuka semacam itu?

    Kemudian, @ marcus-junius-brutus (jawaban teratas) akan menghasilkan kesalahan rekursi tak terbatas. Dalam hal ini, kita dapat melakukan sesuatu seperti di bawah ini.

    Kita harus membuat konstruktor salinan di dalam kelas dasar dan subkelas pembungkus sebagai berikut:

.

// Base class(modified)
public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }
    // COPY CONSTRUCTOR
    public Cat(Cat cat) {
        this.name = cat.name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}



    // The wrapper subclass for serialization
public class CatWrapper extends Cat{


    public CatWrapper(String name) {
        super(name);
    }

    public CatWrapper(Cat cat) {
        super(cat);
    }
}

Dan serializer untuk tipe Cat:

public class CatSerializer implements JsonSerializer<Cat> {

    @Override
    public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {

        // Essentially the same as the type Cat
        JsonElement catWrapped = context.serialize(new CatWrapper(src));

        // Here, we can customize the generated JSON from the wrapper as we want.
        // We can add a field, remove a field, etc.


        return modifyJSON(catWrapped);
    }

    private JsonElement modifyJSON(JsonElement base){
        // TODO: Modify something
        return base;
    }
}

Jadi, mengapa pembuat salinan?

Nah, setelah Anda menentukan konstruktor salinan, tidak peduli seberapa banyak kelas dasar berubah, pembungkus Anda akan melanjutkan dengan peran yang sama. Kedua, jika kita tidak mendefinisikan konstruktor salinan dan hanya membuat subkelas kelas dasar maka kita harus "berbicara" dalam istilah kelas yang diperluas, yaitu,CatWrapper . Sangat mungkin bahwa komponen Anda berbicara dalam istilah kelas dasar dan bukan jenis pembungkusnya.

Apakah ada alternatif yang mudah?

Tentu, sekarang telah diperkenalkan oleh Google - inilah RuntimeTypeAdapterFactoryimplementasinya:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

Di sini, Anda perlu memasukkan Animalkolom yang disebut "type" in dan nilai di dalamnya yang sama Dogmenjadi "dog", Catmenjadi "cat"

Contoh lengkap: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html

  • Kasus 2: Anda tidak mengontrol kelas . Anda bergabung dengan perusahaan atau menggunakan pustaka di mana kelas sudah ditentukan dan manajer Anda tidak ingin Anda mengubahnya dengan cara apa pun - Anda dapat membuat subkelas kelas Anda dan meminta mereka menerapkan antarmuka penanda umum (yang tidak memiliki metode apa pun ) seperti AnimalInterface.

    Ex:

.

// The class we are NOT allowed to modify

public class Dog implements IAnimal {

    public String name;
    public int ferocity;

    public Dog(String name, int ferocity) {
        super();
        this.name = name;
        this.ferocity = ferocity;
    }

    @Override
    public String sound() {
        return name + " : \"bark\" (ferocity level:" + ferocity + ")";
    }
}


// The marker interface

public interface AnimalInterface {
}

// The subclass for serialization

public class DogWrapper  extends Dog implements AnimalInterface{

    public DogWrapper(String name, int ferocity) {
        super(name, ferocity);
    }

}

// The subclass for serialization

public class CatWrapper extends Cat implements AnimalInterface{


    public CatWrapper(String name) {
        super(name);
    }
}

Jadi, kami akan menggunakan CatWrapperalih- alih Cat, DogWrapperalih- alih Dogdan AlternativeAnimalAdapteralih-alihIAnimalAdapter

// The only difference between `IAnimalAdapter` and `AlternativeAnimalAdapter` is that of the interface, i.e, `AnimalInterface` instead of `IAnimal`

public class AlternativeAnimalAdapter implements JsonSerializer<AnimalInterface>, JsonDeserializer<AnimalInterface> {

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(AnimalInterface src, Type typeOfSrc,
                                 JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src); 
        retValue.add(INSTANCE, elem);
        return retValue;
    }

    @Override
    public AnimalInterface deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException  {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

Kami melakukan tes:

public class Test {

    public static void main(String[] args) {

        // Note that we are using the extended classes instead of the base ones
        IAnimal animals[] = new IAnimal[]{new CatWrapper("Kitty"), new DogWrapper("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(AnimalInterface.class, new AlternativeAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, AnimalInterface.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            AnimalInterface animal2 = gsonExt.fromJson(animalJson, AnimalInterface.class);
        }
    }
}

Keluaran:

serialized with the custom serializer:{"CLASSNAME":"com.examples_so.CatWrapper","INSTANCE":{"name":"Kitty"}}
serialized with the custom serializer:{"CLASSNAME":"com.examples_so.DogWrapper","INSTANCE":{"name":"Brutus","ferocity":5}}
Manish Kumar Sharma
sumber
1

Jika Anda ingin mengelola TypeAdapter untuk sebuah tipe dan yang lainnya untuk sub tipe-nya, Anda dapat menggunakan TypeAdapterFactory seperti ini:

public class InheritanceTypeAdapterFactory implements TypeAdapterFactory {

    private Map<Class<?>, TypeAdapter<?>> adapters = new LinkedHashMap<>();

    {
        adapters.put(Animal.class, new AnimalTypeAdapter());
        adapters.put(Dog.class, new DogTypeAdapter());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        TypeAdapter<T> typeAdapter = null;
        Class<?> currentType = Object.class;
        for (Class<?> type : adapters.keySet()) {
            if (type.isAssignableFrom(typeToken.getRawType())) {
                if (currentType.isAssignableFrom(type)) {
                    currentType = type;
                    typeAdapter = (TypeAdapter<T>)adapters.get(type);
                }
            }
        }
        return typeAdapter;
    }
}

Pabrik ini akan mengirimkan TypeAdapter yang paling akurat

r3n0j
sumber
0

Jika Anda menggabungkan jawaban Marcus Junius Brutus dengan edit user2242263, Anda dapat menghindari keharusan menentukan hierarki kelas yang besar di adaptor Anda dengan mendefinisikan adaptor Anda sebagai bekerja pada tipe antarmuka. Anda kemudian dapat memberikan implementasi default toJSON () dan fromJSON () di antarmuka Anda (yang hanya menyertakan dua metode ini) dan meminta setiap kelas yang Anda butuhkan untuk membuat serialisasi mengimplementasikan antarmuka Anda. Untuk menangani casting, di subclass Anda, Anda dapat menyediakan metode fromJSON () statis yang deserialisasi dan melakukan transmisi yang sesuai dari jenis antarmuka Anda. Ini bekerja dengan sangat baik untuk saya (berhati-hatilah dengan membuat serial / deserialisasi kelas yang berisi hashmaps - tambahkan ini saat Anda membuat instantiate gson builder Anda:

GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();

Semoga ini membantu seseorang menghemat waktu dan tenaga!

sodapoparooni
sumber