java - new - spring boot convert object to xml string
Jackson JSON Modificar objeto antes de la serialización (3)
Estoy buscando modificar un objeto justo antes de que se serialice. Quiero escribir un serializador personalizado para analizar el objeto y luego pasarlo al serializador de objetos predeterminado.
Esto es lo que tengo:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
*
* @author Me
*/
public class PersonSerializer extends JsonSerializer<Person>{
@Override
public void serialize(Person value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
//This returns a modified clone of Person value.
Person safePerson = PrivacyService.getSafePerson(value);
provider.defaultSerializeValue(safePerson, jgen);
}
}
Pero eso solo entra en un ciclo infinate. También he intentado:
provider.findTypedValueSerializer(Person.class, true, null).serialize(safePerson, jgen, provider);
Eso funciona, pero no analiza ninguno de los campos en el objeto.
También traté de usar un @JsonFilter
pero era extremadamente pesado y sextuplicaba mis tiempos de carga.
¡Ayuda! ¡Gracias!
Santa mierda, después de varias horas de cavar a través de esta biblioteca, tratando de escribir mi propia fábrica, y un millar de otras cosas, finalmente conseguí esta estúpida cosa para hacer lo que quería:
public class PersonSerializer extends JsonSerializer<Person>{
@Override
public void serialize(Person value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
Person safePerson = PrivacyService.getSafePerson(value);
//This is the crazy one-liner that will save someone a very long time
BeanSerializerFactory.instance.createSerializer(provider, SimpleType.construct(Person.class)).serialize(safePerson, jgen, provider);
}
}
Desde Jackson 2.2 uno puede usar el convertidor en la anotación JsonSerialize:
@JsonSerialize(converter = OurConverter.class)
y el convertidor
public class OurConverter extends StdConverter<IN, OUT>
IN y OUT son la misma clase si se modifica el objeto
Aunque inicialmente me regocijé por encontrar la respuesta de @Nitroware, desafortunadamente no funciona en Jackson 2.7.2 - BeanSerializerFactory.instance.createSerializer
introspecta la anotación JsonSerializer
en la clase Person
nuevamente, lo que lleva a la recursión infinita y Error
.
El punto donde se crearía el serializador predeterminado si @JsonSerializer
no @JsonSerializer
en la clase POJO es el método BeanSerializerFactory.constructBeanSerializer
. Entonces, usemos este método directamente. Dado que el método está protected
, lo hacemos visible a través de la subclase de fábrica y lo alimentamos con información sobre la clase serializada. Además, reemplazamos el método SimpleType.construct
desuso por su reemplazo recomendado. La solución completa es:
public class PersonSerializer extends JsonSerializer<PersonSerializer> {
static class BeanSerializerFactoryWithVisibleConstructingMethod extends BeanSerializerFactory {
BeanSerializerFactoryWithVisibleConstructingMethod() {
super(BeanSerializerFactory.instance.getFactoryConfig());
}
@Override
public JsonSerializer<Object> constructBeanSerializer(SerializerProvider prov, BeanDescription beanDesc) throws JsonMappingException {
return super.constructBeanSerializer(prov, beanDesc);
}
}
private final BeanSerializerFactoryWithVisibleConstructingMethod defaultBeanSerializerFactory = new BeanSerializerFactoryWithVisibleConstructingMethod();
private final JavaType javaType = TypeFactory.defaultInstance().constructType(Person.class);
@Override
public void serialize(Person value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
Person safePerson = PrivacyService.getSafePerson(value);
JavaType type = TypeFactory.defaultInstance().constructType(Person.class);
BeanDescription beanDescription = provider.getConfig().introspect(type);
JsonSerializer<Object> defaultSerializer = defaultBeanSerializerFactory.constructBeanSerializer(provider, beanDescription);
defaultSerializer.serialize(safePerson, jgen, provider);
}
}
A diferencia de la solución basada en BeanSerializerModifier
, donde se le obliga a declarar y registrar un comportamiento especial fuera de su serializador personalizado, con esta solución, la lógica especial aún se encapsula en PersonSerializer
personalizado.
Eventualmente, la lógica podría ser empujada al antecesor DefaultJsonSerializerAware
personalizado.
ACTUALIZACIÓN 2017-09-28:
Encontré un error en el razonamiento indicado anteriormente. El uso del método único BeanSerializerFactory.constructBeanSerializer
no es suficiente. Si la clase original contiene campos nulos, no están en salida. (La razón es que el método constructBeanSerializer
se llama indirectamente desde el método createAndCacheUntypedSerializer
que posteriormente llama addAndResolveNonTypedSerializer
método NullSerializer
donde se agregan BeanPropertyWriter
a BeanPropertyWriter
s).)
La solución a este problema que me parece correcta y que es bastante simple es reutilizar toda la lógica de serialización, no solo el método constructBeanSerializer . Esta lógica comienza en el método serializeValue
del proveedor. Lo único inapropiado es la anotación JsonSerialize
personalizada. Por lo tanto, redefinimos BeanSerializationFactory
para simular la clase introspected (y solo esto; de lo contrario, las anotaciones de JsonSerialize en los tipos de campo no se aplicarían) no tiene ninguna anotación JsonSerialize
.
@Override
public void serialize(Person value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
Person safePerson = PrivacyService.getSafePerson(value);
ObjectMapper objectMapper = (ObjectMapper)jgen.getCodec();
Class<?> entityClass = value.getClass();
JavaType javaType = TypeFactory.defaultInstance().constructType(entityClass);
DefaultSerializerProvider.Impl defaultSerializerProvider = (DefaultSerializerProvider.Impl) objectMapper.getSerializerProviderInstance();
BeanSerializerFactory factoryIgnoringCustomSerializerOnRootClass = new BeanSerializerFactory(BeanSerializerFactory.instance.getFactoryConfig()) {
@Override
protected JsonSerializer<Object> findSerializerFromAnnotation(SerializerProvider prov, Annotated a) throws JsonMappingException {
JsonSerializer<Object> result = javaType.equals(a.getType()) ? null : super.findSerializerFromAnnotation(prov, a);
return result;
}
};
DefaultSerializerProvider.Impl updatedSerializerProvider = defaultSerializerProvider.createInstance(defaultSerializerProvider.getConfig(), factoryIgnoringCustomSerializerOnRootClass);
updatedSerializerProvider.serializeValue(jgen, value);
}
Tenga en cuenta que si no sufre un problema con valores nulos, la solución anterior es suficiente para usted.