Deserializzazione di una classe astratta in GSON
-
26-09-2019 - |
Domanda
Ho un oggetto albero in formato JSON, sto cercando di deserializzare con GSON. Ogni nodo contiene i suoi nodi figlio come campi del nodo di tipo oggetto. Node è un'interfaccia, che ha diverse implementazioni di classe concreta. Durante il processo di deserializzazione, come posso comunicare a GSON quale classe concreta implementare durante la deseriazione del nodo, se non conosco a priori a quale tipo appartiene il nodo? Ogni nodo ha un campo membro che specifica il tipo. C'è un modo per accedere al campo quando l'oggetto è in forma serializzata e in qualche modo comunica il tipo a GSON?
Grazie!
Soluzione
Suggerirei di aggiungere un'usanza Jsondeserializer per Node
S:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
Sarai in grado di accedere al JsonElement
Rappresentando il nodo nel metodo del deserializzatore, convertilo in a JsonObject
, e recupera il campo che specifica il tipo. È quindi possibile creare un'istanza del tipo corretto di Node
basato su questo.
Altri suggerimenti
Dovrai registrare sia JSonserializer che JSondeserializer. Inoltre puoi implementare un adattatore generico per tutte le tue interfacce nel modo seguente:
- Durante la serializzazione: aggiungere un meta-info del tipo di classe implicativo effettivo.
- Durante la deserializzazione: recuperare quel meta informativo e chiamare il jsondeseralize di quella classe
Ecco l'implementazione che ho usato per me e funziona bene.
public class PropertyBasedInterfaceMarshal implements
JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_META_KEY = "CLASS_META_KEY";
@Override
public Object deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_META_KEY).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
@Override
public JsonElement serialize(Object object, Type type,
JsonSerializationContext jsonSerializationContext) {
JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass());
jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY,
object.getClass().getCanonicalName());
return jsonEle;
}
}
Quindi potresti registrare questo adattatore per tutte le tue interfacce come segue
Gson gson = new GsonBuilder()
.registerTypeAdapter(IInterfaceOne.class,
new PropertyBasedInterfaceMarshal())
.registerTypeAdapter(IInterfaceTwo.class,
new PropertyBasedInterfaceMarshal()).create();
Per quanto posso dire, questo non funziona per i tipi di non collezione, o più specificamente, situazioni in cui il tipo di calcestruzzo viene utilizzato per serializzare e il tipo di interfaccia viene utilizzato per deserializzare. Cioè, se hai una semplice classe che implementa un'interfaccia e seriali la classe concreta, quindi specifica l'interfaccia per deserializzare, finirai in una situazione irrecuperabile.
Nell'esempio sopra l'adattatore di tipo è registrato rispetto all'interfaccia, ma quando si seria utilizzando la classe concreta non verrà utilizzato, il che significa che i dati Class_Meta_key non verranno mai impostati.
Se si specifica l'adattatore come adattatore gerarchico (dicendo così a GSON di usarlo per tutti i tipi nella gerarchia), finirai in un ciclo infinito poiché il serializzatore continuerà a chiamare se stesso.
Qualcuno sa come serializzare da un'implementazione concreta di un'interfaccia, quindi deserializza usando solo l'interfaccia e un incantatore?
Per impostazione predefinita sembra che GSON creerà l'istanza concreta, ma non imposterà i suoi campi.
Il problema è registrato qui:
http://code.google.com/p/google-gson/issues/detail?id=411&q=Interface
Devi usare TypeToken
Classe di Google GSON. Avrai bisogno ovviamente che abbia una classe T generica per farlo funzionare
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.tojson (foo, sitype);
gson.fromJson(json, fooType);