Zaktualizowana odpowiedź - najlepsze części wszystkich innych odpowiedzi
Ja opisuję rozwiązań dla różnych przypadków użycia i będzie zajęcie się nieskończonej rekurencji problem jak dobrze
Przypadek 1: Jesteś w kontroli klas , czyli można dostać się do pisania własnych Cat
, Dog
zajęcia, jak również IAnimal
interfejs. Możesz po prostu zastosować rozwiązanie podane przez @ marcus-junius-brutus (najwyżej oceniana odpowiedź)
Nie będzie nieskończonej rekurencji, jeśli istnieje wspólny interfejs podstawowy jako IAnimal
Ale co, jeśli nie chcę implementować takiego IAnimal
lub innego interfejsu?
Wtedy @ marcus-junius-brutus (najwyżej oceniona odpowiedź) spowoduje nieskończony błąd rekurencji. W takim przypadku możemy zrobić coś takiego jak poniżej.
Musielibyśmy utworzyć konstruktor kopiujący wewnątrz klasy bazowej i podklasę opakowującą w następujący sposób:
.
// 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);
}
}
I serializator dla typu 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;
}
}
Dlaczego więc konstruktor kopiujący?
Cóż, po zdefiniowaniu konstruktora kopiującego, bez względu na to, jak bardzo zmieni się klasa bazowa, opakowanie będzie nadal pełniło tę samą rolę. Po drugie, gdybyśmy nie zdefiniowali konstruktora kopiującego i po prostu podklasowali klasę bazową, musielibyśmy „rozmawiać” w kategoriach klasy rozszerzonej, tj CatWrapper
. , . Jest całkiem możliwe, że komponenty mówią w kategoriach klasy bazowej, a nie typu opakowania.
Czy istnieje łatwa alternatywa?
Jasne, zostało wprowadzone przez Google - oto RuntimeTypeAdapterFactory
implementacja:
RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(runtimeTypeAdapterFactory)
.create();
Tutaj musisz wprowadzić pole o nazwie „typ” Animal
i wartość tego samego wnętrza, Dog
aby było „pies”, Cat
aby było „kotem”
Pełny przykład: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html
Przypadek 2: Nie kontrolujesz zajęć . Dołączasz do firmy lub korzystasz z biblioteki, w której klasy są już zdefiniowane, a Twój menedżer nie chce, abyś w jakikolwiek sposób je zmieniał - Możesz podklasować swoje klasy i zaimplementować wspólny interfejs znaczników (który nie ma żadnych metod ), takie jak AnimalInterface
.
Dawny:
.
// 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);
}
}
Więc używalibyśmy CatWrapper
zamiast Cat
, DogWrapper
zamiast Dog
i
AlternativeAnimalAdapter
zamiastIAnimalAdapter
// 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);
}
}
Wykonujemy test:
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);
}
}
}
Wynik:
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}}