java - leer - Seralizador personalizado de Gson para una variable(de muchas) en un objeto que usa TypeAdapter
json java gson (2)
He visto muchos ejemplos simples de usar un TypeAdapter personalizado. El más útil ha sido Class TypeAdapter<T>
. Pero eso no ha respondido mi pregunta todavía.
Quiero personalizar la serialización de un solo campo en el objeto y dejar que el mecanismo Gson predeterminado se encargue del resto.
Para fines de discusión, podemos utilizar esta definición de clase como la clase del objeto que deseo serializar. Quiero que Gson serialice los dos primeros miembros de la clase, así como todos los miembros expuestos de la clase base, y quiero hacer una serialización personalizada para el miembro de la clase tercero y final que se muestra a continuación.
public class MyClass extends SomeClass {
@Expose private HashMap<String, MyObject1> lists;
@Expose private HashMap<String, MyObject2> sources;
private LinkedHashMap<String, SomeClass> customSerializeThis;
[snip]
}
Esta es una gran pregunta porque aísla algo que debería ser fácil, pero en realidad requiere mucho código.
Para comenzar, escriba un resumen TypeAdapterFactory
que le da ganchos para modificar los datos salientes. Este ejemplo usa una API nueva en Gson 2.2 llamada getDelegateAdapter()
que le permite buscar el adaptador que Gson usaría de forma predeterminada. Los adaptadores de delegado son extremadamente útiles si solo quiere ajustar el comportamiento estándar. Y a diferencia de los adaptadores de tipo personalizados, 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. Del mismo modo para la deserialización con afterRead()
.
Luego, subclasificamos esto para el ejemplo específico de MyClass
. Para ilustrar, agregaré una propiedad sintética llamada ''tamaño'' al mapa cuando se serialice. Y por simetría lo eliminaré cuando se deserialice. 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, junten todo creando una instancia personalizada de Gson
que use el nuevo adaptador de tipo:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new MyClassTypeAdapterFactory())
.create();
Los nuevos tipos de TypeAdapter y TypeAdapterFactory son extremadamente potentes, pero también son abstractos y TypeAdapterFactory práctica para usarlos de manera efectiva. ¡Espero que este ejemplo te sea útil!
Hay otro enfoque para esto. Como dice Jesse Wilson, se supone que es fácil. Y adivina qué, es fácil!
Si implementa JsonSerializer
y JsonDeserializer
para su tipo, puede manejar las partes que desea y delegar a Gson para todo lo demás , con muy poco código. Estoy citando la respuesta de @ Perception en otra pregunta a continuación para mayor comodidad, consulte esa respuesta para obtener más detalles:
En este caso, es mejor utilizar un
JsonSerializer
en lugar de unTypeAdapter
, por la sencilla razón de que los serializadores tienen acceso a su contexto de serialización.
public class PairSerializer implements JsonSerializer<Pair> { @Override public JsonElement serialize(final Pair value, final Type type, final JsonSerializationContext context) { final JsonObject jsonObj = new JsonObject(); jsonObj.add("first", context.serialize(value.getFirst())); jsonObj.add("second", context.serialize(value.getSecond())); return jsonObj; } }
La principal ventaja de esto (además de evitar soluciones complicadas) es que aún puede aprovechar otros adaptadores de tipo y serializadores personalizados que podrían haberse registrado en el contexto principal. Tenga en cuenta que el registro de serializadores y adaptadores usa exactamente el mismo código.
Sin embargo, reconoceré que el enfoque de Jesse se ve mejor si con frecuencia va a modificar campos en su objeto Java. Es una compensación de la facilidad de uso frente a la flexibilidad, haga su elección.