Делегирование в пользовательском десериализаторе Джексона

#java #generics #jackson #json-deserialization

Вопрос:

Ситуация следующая: мне удалось заставить Джексона десериализовать следующий общий ResponseWrapper<T> код .

 static final class ResponseWrapper<T> {
    
    private ResponseData <T> response;
    
    protected static final class ResponseData<T> {
    
        private int    status;
        private T      data;
    
    }
    
}
 

Используя следующие ParameterizedTypeReference

 public static <T> ParameterizedTypeReference <ResponseWrapper<T>> typeReferenceOf ( Class<T> tClass ) {
    return ParameterizedTypeReference.forType( ParameterizedTypeImpl.make( ResponseWrapper.class, new Type[]{ tClass }, null ) );
}
 

Проблема: мне нужно справиться с ситуацией, когда десерализация T завершится неудачей, потому что значение data будет не объектом, а String вместо него. Мне нужно зафиксировать исключение и присвоить это значение другому свойству ResponseData , например String errorMessage . Я взял это на себя, чтобы аннотировать response свойство, @JsonDeserialize( using = ResponseDeserializer.class ) но я не знаю, как правильно реализовать JsonDeserializer , чтобы оно делегировало десериализацию реализациям Джексона, и я просто фиксирую исключение, когда оно возникает.

Для большего контекста я использую WebClient в качестве HTTP-клиента и обрабатываю ответы с помощью обмена Function<ClientResponse, Mono<ResponseWrapper<T>> , в котором происходит десериализация.

Ответ №1:

Чтобы получить универсальный тип, мне пришлось ResponseDeserializer реализовать ContextualDeserializer

 @Override
public JsonDeserializer<?> createContextual ( DeserializationContext context, BeanProperty property ) throws JsonMappingException {

   JavaType wrapperType = property.getType();
   JavaType valueType = wrapperType.containedType(0);
   return new ResponseDeserializer<>( valueType );

}
 

Это позволило мне создать конкретные экземпляры десериализатора с конкретными JavaType . Ниже приведено полное решение:

 private static final class ResponseDeserializer<T> extends JsonObjectDeserializer<ResponseWrapper.ResponseData<T>> implements ContextualDeserializer  {
   
   private final JavaType javaType;

   public ResponseDeserializer ( ) {
       this.javaType = null;
   }
   
   public ResponseDeserializer ( JavaType javaType ) {
       this.javaType = javaType;
   }
   
   @Override
   public JsonDeserializer<?> createContextual ( DeserializationContext context, BeanProperty property ) throws JsonMappingException {

       JavaType wrapperType = property.getType();
       JavaType valueType = wrapperType.containedType(0);
       return new ResponseDeserializer<>( valueType );
       
   }

   @Override
   protected ResponseWrapper.ResponseData<T> deserializeObject ( JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, JsonNode tree ) throws IOException {
       
       int status = tree.get( "status" ).asInt();
       JsonNode dataNode = tree.get( "data" );
       
       try {

           final T data; {
               
               if ( dataNode == null ) {
                   data = null;
               } else {
                   JsonParser dataParser = dataNode.traverse();
                   JsonToken token = dataParser.nextToken(); // handle?
                   data = context.readValue( dataParser, javaType );
               }
               
           };
           
           return new ResponseWrapper.ResponseData<T>( status, data );
           
       } catch ( InvalidDefinitionException e ) {
           
           String errorMessage = dataNode.asText();
           return new ResponseWrapper.ResponseData<T>( status, errorMessage );
           
       }

   }
   
}