Questa è un'ottima domanda perché isola qualcosa che dovrebbe essere facile ma che in realtà richiede molto codice.
Per iniziare, scrivi un abstract TypeAdapterFactory
che ti dia degli hook per modificare i dati in uscita. Questo esempio utilizza una nuova API in Gson 2.2 chiamata getDelegateAdapter()
che consente di cercare l'adattatore che Gson userebbe per impostazione predefinita. Gli adattatori delegato sono estremamente utili se si desidera modificare il comportamento standard. E a differenza degli adattatori di tipo completamente personalizzati, rimarranno aggiornati automaticamente man mano che aggiungi e rimuovi i campi.
public abstract class CustomizedTypeAdapterFactory<C>
implements TypeAdapterFactory {
private final Class<C> customizedClass;
public CustomizedTypeAdapterFactory(Class<C> customizedClass) {
this.customizedClass = customizedClass;
}
@SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal
public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return type.getRawType() == customizedClass
? (TypeAdapter<T>) customizeMyClassAdapter(gson, (TypeToken<C>) type)
: null;
}
private TypeAdapter<C> customizeMyClassAdapter(Gson gson, TypeToken<C> type) {
final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<C>() {
@Override public void write(JsonWriter out, C value) throws IOException {
JsonElement tree = delegate.toJsonTree(value);
beforeWrite(value, tree);
elementAdapter.write(out, tree);
}
@Override public C read(JsonReader in) throws IOException {
JsonElement tree = elementAdapter.read(in);
afterRead(tree);
return delegate.fromJsonTree(tree);
}
};
}
/**
* Override this to muck with {@code toSerialize} before it is written to
* the outgoing JSON stream.
*/
protected void beforeWrite(C source, JsonElement toSerialize) {
}
/**
* Override this to muck with {@code deserialized} before it parsed into
* the application type.
*/
protected void afterRead(JsonElement deserialized) {
}
}
La classe precedente utilizza la serializzazione predefinita per ottenere un albero JSON (rappresentato da JsonElement
), quindi chiama il metodo hook beforeWrite()
per consentire alla sottoclasse di personalizzare quell'albero. Allo stesso modo per la deserializzazione con afterRead()
.
Successivamente lo sottoclassiamo per l' MyClass
esempio specifico . Per illustrare, aggiungerò una proprietà sintetica chiamata "size" alla mappa quando viene serializzata. E per la simmetria lo rimuoverò quando sarà deserializzato. In pratica questa potrebbe essere una qualsiasi personalizzazione.
private class MyClassTypeAdapterFactory extends CustomizedTypeAdapterFactory<MyClass> {
private MyClassTypeAdapterFactory() {
super(MyClass.class);
}
@Override protected void beforeWrite(MyClass source, JsonElement toSerialize) {
JsonObject custom = toSerialize.getAsJsonObject().get("custom").getAsJsonObject();
custom.add("size", new JsonPrimitive(custom.entrySet().size()));
}
@Override protected void afterRead(JsonElement deserialized) {
JsonObject custom = deserialized.getAsJsonObject().get("custom").getAsJsonObject();
custom.remove("size");
}
}
Infine metti tutto insieme creando Gson
un'istanza personalizzata che utilizza il nuovo adattatore di tipo:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new MyClassTypeAdapterFactory())
.create();
I nuovi tipi TypeAdapter e TypeAdapterFactory di Gson sono estremamente potenti, ma sono anche astratti e richiedono pratica per essere utilizzati in modo efficace. Spero che trovi utile questo esempio!