Esta es una gran pregunta porque aísla algo que debería ser fácil pero que en realidad requiere mucho código.
Para comenzar, escriba un resumen TypeAdapterFactory
que le proporcione ganchos para modificar los datos salientes. Este ejemplo usa una nueva API en Gson 2.2 llamada getDelegateAdapter()
que le permite buscar el adaptador que Gson usaría por defecto. Los adaptadores de delegado son extremadamente útiles si solo desea modificar el comportamiento estándar. Y a diferencia de los adaptadores de tipo personalizados completos, se mantendrán actualizados automáticamente a medida que agregue y elimine campos.
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 clase anterior usa la serialización predeterminada para obtener un árbol JSON (representado por JsonElement
), y luego llama al método hook beforeWrite()
para permitir que la subclase personalice ese árbol. De manera similar para la deserialización con afterRead()
.
A continuación, subclasificamos esto para el MyClass
ejemplo específico . Para ilustrar, agregaré una propiedad sintética llamada 'tamaño' al mapa cuando esté serializado. Y por simetría, lo eliminaré cuando esté deserializado. En la práctica, esto podría ser cualquier personalización.
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");
}
}
Finalmente, júntelo todo creando una Gson
instancia personalizada que use el nuevo adaptador de tipo:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new MyClassTypeAdapterFactory())
.create();
Los nuevos tipos TypeAdapter y TypeAdapterFactory de Gson son extremadamente poderosos, pero también son abstractos y requieren práctica para usarlos de manera efectiva. ¡Esperamos que este ejemplo le resulte útil!