Como criar um desserializador personalizado em Jackson para um tipo genérico?

Imagine o seguinte cenário:

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

Eu quero escrever um custom deserializer de Jackson para Foo. Para fazer isso (por exemplo, para desserializar a class Bar que tem a propriedade Foo ), eu preciso saber o tipo concreto de Foo , usado em Bar , no tempo de desserialização (por exemplo, eu preciso saber que T é Something nesse caso particular).

Como se escreve um desserializador? Deveria ser possível fazê-lo, já que Jackson faz isso com collections e mapas typescripts.

Esclarecimentos:

Parece que existem 2 partes para solução do problema:

1) Obtenha o tipo declarado de propriedade foo dentro de Bar e use isso para desserializar Foo

2) Descobrir no momento da desserialização que estamos desserializando a propriedade foo dentro da Bar classs para concluir com êxito a etapa 1)

Como um completo 1 e 2?

Você pode implementar um JsonDeserializer personalizado para o seu tipo genérico que também implementa o ContextualDeserializer .

Por exemplo, suponha que tenhamos o seguinte tipo de wrapper simples que contém um valor genérico:

 public static class Wrapper { public T value; } 

Agora queremos desserializar o JSON que se parece com isso:

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

em uma instância de uma class que se parece com isso:

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

A implementação de ContextualDeserializer nos permite criar um desserializador específico para cada campo na class Person , com base nos parâmetros de tipo genérico do campo. Isso nos permite desserializar o nome como uma string e a idade como um inteiro.

O desserializador completo é assim:

 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; } } 

É melhor olhar para createContextual aqui primeiro, pois isso será chamado primeiro por Jackson. Nós lemos o tipo do campo fora do BeanProperty (por exemplo, Wrapper ) e, em seguida, extraímos o primeiro parâmetro de tipo genérico (por exemplo, String ). Em seguida, criamos um novo desserializador e armazenamos o tipo interno como o valueType .

Uma vez deserialize é chamado neste desserializador recém-criado, podemos simplesmente pedir a Jackson para desserializar o valor como o tipo interno em vez de como o tipo de wrapper inteiro e retornar um novo Wrapper contendo o valor desserializado.

Para registrar este desserializador customizado, precisamos criar um módulo que o contenha e registrar esse módulo:

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

Se, em seguida, tentarmos desserializar o exemplo JSON acima, podemos ver que ele funciona conforme o esperado:

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

Há mais alguns detalhes sobre como os desserializadores contextuais funcionam na documentação de Jackson .

Se o destino em si for um tipo genérico, a propriedade será nula, para isso você precisará obter o valueTtype do 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; } }