There’s another angle to tackle this problem more generically for objects that would be deserialized using the BeanDeserializer, by creating a BeanDeserializerModifier
and registering it with your mapper. BeanDeserializerModifier
is a sort of alternative to subclassing BeanDeserializerFactory
, and it gives you a chance to return something other than the normal deserializer that would be used, or to modify it.
So, first create a new JsonDeserializer
that can accept another deserializer when it’s being constructed, and then holds on to that serializer. In the deserialize method, you can check if you’re being passed a JsonParser
that's currently pointing at a JsonToken.START_ARRAY
. If you’re not passed JsonToken.START_ARRAY
, then just use the default deserializer that was passed in to this custom deserialize when it was created.
Finally, make sure to implement ResolvableDeserializer
, so that the default deserializer is properly attached to the context that your custom deserializer is using.
class ArrayAsNullDeserialzer extends JsonDeserializer implements ResolvableDeserializer {
JsonDeserializer<?> mDefaultDeserializer;
@Override
/* Make sure the wrapped deserializer is usable in this deserializer's contexts */
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) mDefaultDeserializer).resolve(ctxt);
}
/* Pass in the deserializer given to you by BeanDeserializerModifier */
public ArrayAsNullDeserialzer(JsonDeserializer<?> defaultDeserializer) {
mDefaultDeserializer = defaultDeserializer;
}
@Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonToken firstToken = jp.getCurrentToken();
if (firstToken == JsonToken.START_ARRAY) {
//Optionally, fail if this is something besides an empty array
return null;
} else {
return mDefaultDeserializer.deserialize(jp, ctxt);
}
}
}
Now that we have our generic deserializer hook, let’s create a modifier that can use it. This is easy, just implement the modifyDeserializer
method in your BeanDeserializerModifier. You will be passed the deserializer that would have been used to deserialize the bean. It also passes you the BeanDesc that will be deserialized, so you can control here whether or not you want to handle [] as null for all types.
public class ArrayAsNullDeserialzerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if ( true /* or check beanDesc to only do this for certain types, for example */ ) {
return new ArrayAsNullDeserializer(deserializer);
} else {
return deserializer;
}
}
}
Finally, you’ll need to register your BeanDeserializerModifier with your ObjectMapper. To do this, create a module, and add the modifier in the setup (SimpleModules don’t seem to have a hook for this, unfortunately). You can read more about modules elsewhere, but here’s an example if you don’t already have a module to add to:
Module m = new Module() {
@Override public String getModuleName() { return "MyMapperModule"; }
@Override public Version version() { return Version.unknownVersion(); }
@Override public void setupModule(Module.SetupContext context) {
context.addBeanDeserializerModifier(new ArrayAsNullDeserialzerModifier());
}
};