¿Cómo crear un deserializador personalizado en Jackson para un tipo genérico?

Imagine el siguiente escenario:

class  Foo { .... } class Bar { Foo foo; } 

Quiero escribir un deserializador Jackson personalizado para Foo. Para hacer eso (por ejemplo, para deserializar la clase Bar que tiene la propiedad Foo ), necesito saber el tipo concreto de Foo , utilizado en Bar , en el tiempo de deserialización (p. Ej., Necesito saber que T es Something en ese caso particular).

¿Cómo se escribe uno de esos deserializadores? Debería ser posible hacerlo, ya que Jackson lo hace con colecciones y mapas a máquina.

Aclaraciones:

Parece que hay 2 partes para la solución del problema:

1) Obtener el tipo declarado de propiedad foo dentro de Bar y usarlo para deserializar Foo

2) Averigüe en el momento de la deserialización que estamos deserializando la propiedad foo dentro de la clase Bar para completar con éxito el paso 1)

¿Cómo se completan 1 y 2?

Puede implementar un JsonDeserializer personalizado para su tipo genérico que también implemente ContextualDeserializer .

Por ejemplo, supongamos que tenemos el siguiente tipo de envoltura simple que contiene un valor genérico:

 public static class Wrapper { public T value; } 

Ahora queremos deserializar JSON que se ve así:

 { "name": "Alice", "age": 37 } 

en una instancia de una clase que se ve así:

 public static class Person { public Wrapper name; public Wrapper age; } 

La implementación de ContextualDeserializer nos permite crear un deserializador específico para cada campo en la clase Person , basado en los parámetros de tipo genérico del campo. Esto nos permite deserializar el nombre como una cadena y la edad como un número entero.

El deserializador completo se ve así:

 public static class WrapperDeserializer extends JsonDeserializer> implements ContextualDeserializer { private JavaType valueType; @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { JavaType wrapperType = property.getType(); JavaType valueType = wrapperType.containedType(0); WrapperDeserializer deserializer = new WrapperDeserializer(); deserializer.valueType = valueType; return deserializer; } @Override public Wrapper deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException { Wrapper wrapper = new Wrapper<>(); wrapper.value = ctxt.readValue(parser, valueType); return wrapper; } } 

Lo mejor es mirar createContextual aquí primero, ya que Jackson lo llamará primero. Leemos el tipo de campo de BeanProperty (p. Ej., Wrapper ) y luego extraemos el primer parámetro de tipo genérico (por ejemplo, String ). Luego creamos un nuevo deserializador y almacenamos el tipo interno como valueType .

Una vez que se solicita la deserialize en este deserializador recién creado, podemos simplemente pedirle a Jackson que deserialice el valor como el tipo interno en lugar de como el tipo de envoltura completa, y devolver un nuevo Wrapper contenga el valor deserializado.

Para registrar este deserializador personalizado, debemos crear un módulo que lo contenga y registrarlo:

 SimpleModule module = new SimpleModule() .addDeserializer(Wrapper.class, new WrapperDeserializer()); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(module); 

Si tratamos de deserializar el ejemplo JSON de arriba, podemos ver que funciona como se esperaba:

 Person person = objectMapper.readValue(json, Person.class); System.out.println(person.name.value); // prints Alice System.out.println(person.age.value); // prints 37 

Hay más detalles sobre cómo funcionan los deserializadores contextuales en la documentación de Jackson .

Si el objective en sí es un tipo genérico, la propiedad será nula, para eso tendrá que obtener el valueTtype del DeserializationContext:

 @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { if (property == null) { // context is generic JMapToListParser parser = new JMapToListParser(); parser.valueType = ctxt.getContextualType().containedType(0); return parser; } else { // property is generic JavaType wrapperType = property.getType(); JavaType valueType = wrapperType.containedType(0); JMapToListParser parser = new JMapToListParser(); parser.valueType = valueType; return parser; } }