Although I initially rejoiced over finding the @Nitroware's answer, it unfortunately does not work in Jackson 2.7.2 - BeanSerializerFactory.instance.createSerializer
introspects JsonSerializer
annotation on Person
class again, which leads to infinite recursion and StackOverflowError
.
The point where default serializer would be created if @JsonSerializer
were absent on POJO class is the BeanSerializerFactory.constructBeanSerializer
method. So let's just use this method directly. Since the method is protected
, we make it visible via factory subclass and feed it with information about serialized class. Also, we replace deprecated SimpleType.construct
method by its recommended replacement. Whole solution is:
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);
}
}
Unlike BeanSerializerModifier
-based solution where you are forced to declare and register special behaviour outside your custom serializer, with this solution special logic is still encapsulated in custom PersonSerializer
.
Eventually the logic might be pushed up to custom DefaultJsonSerializerAware
ancestor.
UPDATE 2017-09-28:
I found bug in reasoning stated above. Using sole BeanSerializerFactory.constructBeanSerializer
method is not enough. If original class contains null fields, they are not in output. (The reason is the constructBeanSerializer
method is indirectly called from createAndCacheUntypedSerializer
method which later calls addAndResolveNonTypedSerializer
method where NullSerializer
s are added into BeanPropertyWriter
s).)
Solution to this problem which seems correct to me and is quite simple is to reuse all serialization logic, not only constructBeanSerializer method. This logic starts in provider's serializeValue
method. The only inappropriate thing is custom JsonSerialize
annotation. So we redefine BeanSerializationFactory
to pretend the introspected class (and only it - otherwise JsonSerialize annotations on field types would not apply) has no JsonSerialize
annotation.
@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);
}
Note if you don't suffer with problem with nulls, previous solution is enough for you.